From 276b723c5b91c61d69a27a67532e29c9ed971ba4 Mon Sep 17 00:00:00 2001 From: Vectorized Date: Fri, 23 Aug 2024 12:37:34 +0000 Subject: [PATCH 1/2] Add EXTCODE contracts --- .gas-snapshot | 95 +++-- test/Extcode.t.sol | 81 ++++ test/utils/Brutalizer.sol | 839 ++++++++++++++++++++++++++++++++++++++ test/utils/TestPlus.sol | 664 ++++++++++++++++++++++-------- 4 files changed, 1456 insertions(+), 223 deletions(-) create mode 100644 test/Extcode.t.sol create mode 100644 test/utils/Brutalizer.sol diff --git a/.gas-snapshot b/.gas-snapshot index 52ff45a..9171dfe 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,95 +1,100 @@ -LibStringTest:testAddressToHexStringZeroRightPadded(address) (runs: 256, μ: 3384, ~: 3384) +ExtcodeTest:testExtcodeInitCodes() (gas: 129646) +ExtcodeTest:testExtcodecopy(uint256,uint256) (runs: 257, μ: 28153, ~: 11129) +ExtcodeTest:testExtcodehash() (gas: 5811) +ExtcodeTest:testExtcodesize() (gas: 5799) +ExtcodeTest:test__codesize() (gas: 4946) +LibStringTest:testAddressToHexStringZeroRightPadded(address) (runs: 257, μ: 3384, ~: 3384) LibStringTest:testBytesToHexString() (gas: 6422) -LibStringTest:testBytesToHexString(bytes) (runs: 256, μ: 690707, ~: 594542) +LibStringTest:testBytesToHexString(bytes) (runs: 257, μ: 686305, ~: 598437) LibStringTest:testBytesToHexStringNoPrefix() (gas: 6039) -LibStringTest:testBytesToHexStringNoPrefix(bytes) (runs: 256, μ: 809930, ~: 594334) +LibStringTest:testBytesToHexStringNoPrefix(bytes) (runs: 257, μ: 673912, ~: 598089) LibStringTest:testContains() (gas: 43812) LibStringTest:testFromAddressToHexString() (gas: 3765) LibStringTest:testFromAddressToHexStringChecksummed() (gas: 40345) -LibStringTest:testFromAddressToHexStringChecksummedDifferential(uint256) (runs: 256, μ: 692226, ~: 583789) +LibStringTest:testFromAddressToHexStringChecksummedDifferential(uint256) (runs: 257, μ: 664371, ~: 587753) LibStringTest:testFromAddressToHexStringWithLeadingZeros() (gas: 3741) -LibStringTest:testHexStringNoPrefixVariants(uint256,uint256) (runs: 256, μ: 641350, ~: 593918) +LibStringTest:testHexStringNoPrefixVariants(uint256,uint256) (runs: 257, μ: 677842, ~: 597628) LibStringTest:testNormalizeSmallString() (gas: 1349) -LibStringTest:testNormalizeSmallString(bytes32) (runs: 256, μ: 3105, ~: 4183) +LibStringTest:testNormalizeSmallString(bytes32) (runs: 257, μ: 2929, ~: 4183) LibStringTest:testStringConcat() (gas: 6735) -LibStringTest:testStringConcat(string,string) (runs: 256, μ: 672637, ~: 575375) +LibStringTest:testStringConcat(string,string) (runs: 257, μ: 674038, ~: 579223) LibStringTest:testStringConcatOriginal() (gas: 7994) LibStringTest:testStringDirectReturn() (gas: 8186) -LibStringTest:testStringDirectReturn(string) (runs: 256, μ: 3585, ~: 3426) +LibStringTest:testStringDirectReturn(string) (runs: 257, μ: 3584, ~: 3426) LibStringTest:testStringEndsWith() (gas: 2798) -LibStringTest:testStringEndsWith(uint256) (runs: 256, μ: 828826, ~: 608504) -LibStringTest:testStringEq(string,string) (runs: 256, μ: 1576, ~: 1577) +LibStringTest:testStringEndsWith(uint256) (runs: 257, μ: 699712, ~: 611578) +LibStringTest:testStringEq(string,string) (runs: 257, μ: 1576, ~: 1577) LibStringTest:testStringEqs() (gas: 2149) LibStringTest:testStringEscapeHTML() (gas: 11844) -LibStringTest:testStringEscapeHTML(uint256) (runs: 256, μ: 779111, ~: 625012) +LibStringTest:testStringEscapeHTML(uint256) (runs: 257, μ: 762467, ~: 617437) LibStringTest:testStringEscapeJSON() (gas: 53068) -LibStringTest:testStringEscapeJSONHexEncode() (gas: 736800) +LibStringTest:testStringEscapeJSONHexEncode() (gas: 740891) LibStringTest:testStringIndexOf() (gas: 17348) -LibStringTest:testStringIndexOf(uint256) (runs: 256, μ: 838879, ~: 619190) +LibStringTest:testStringIndexOf(uint256) (runs: 257, μ: 712791, ~: 618244) LibStringTest:testStringIndicesOf() (gas: 11642) -LibStringTest:testStringIndicesOf(uint256) (runs: 256, μ: 747502, ~: 617517) +LibStringTest:testStringIndicesOf(uint256) (runs: 257, μ: 743540, ~: 621591) LibStringTest:testStringIs7BitASCII() (gas: 203501) -LibStringTest:testStringIs7BitASCIIDifferential(bytes) (runs: 256, μ: 708983, ~: 574187) +LibStringTest:testStringIs7BitASCIIDifferential(bytes) (runs: 257, μ: 668440, ~: 578410) LibStringTest:testStringLastIndexOf() (gas: 23952) -LibStringTest:testStringLastIndexOf(uint256) (runs: 256, μ: 808848, ~: 615043) -LibStringTest:testStringLowerDifferential() (gas: 3618133) -LibStringTest:testStringLowerDifferential(string) (runs: 256, μ: 8873, ~: 8506) +LibStringTest:testStringLastIndexOf(uint256) (runs: 257, μ: 752180, ~: 621949) +LibStringTest:testStringLowerDifferential() (gas: 3212201) +LibStringTest:testStringLowerDifferential(string) (runs: 257, μ: 8857, ~: 8506) LibStringTest:testStringLowerOriginal() (gas: 1771) -LibStringTest:testStringPackAndUnpackOne() (gas: 724714) -LibStringTest:testStringPackAndUnpackOne(string) (runs: 256, μ: 700607, ~: 573980) -LibStringTest:testStringPackAndUnpackOneDifferential(string) (runs: 256, μ: 688368, ~: 573268) -LibStringTest:testStringPackAndUnpackTwo() (gas: 885198) -LibStringTest:testStringPackAndUnpackTwo(string,string) (runs: 256, μ: 661591, ~: 575678) -LibStringTest:testStringPackAndUnpackTwoDifferential(string,string) (runs: 256, μ: 673562, ~: 574227) +LibStringTest:testStringPackAndUnpackOne() (gas: 729214) +LibStringTest:testStringPackAndUnpackOne(string) (runs: 257, μ: 743821, ~: 577878) +LibStringTest:testStringPackAndUnpackOneDifferential(string) (runs: 257, μ: 714090, ~: 577233) +LibStringTest:testStringPackAndUnpackTwo() (gas: 4584957) +LibStringTest:testStringPackAndUnpackTwo(string,string) (runs: 257, μ: 757240, ~: 579613) +LibStringTest:testStringPackAndUnpackTwoDifferential(string,string) (runs: 257, μ: 710280, ~: 578104) LibStringTest:testStringRepeat() (gas: 8445) -LibStringTest:testStringRepeat(string,uint256) (runs: 256, μ: 693370, ~: 576818) +LibStringTest:testStringRepeat(string,uint256) (runs: 257, μ: 670577, ~: 580884) LibStringTest:testStringRepeatOriginal() (gas: 13538) -LibStringTest:testStringReplace(uint256) (runs: 256, μ: 667960, ~: 622556) +LibStringTest:testStringReplace(uint256) (runs: 257, μ: 722961, ~: 623450) LibStringTest:testStringReplaceLong() (gas: 9615) LibStringTest:testStringReplaceMedium() (gas: 8368) LibStringTest:testStringReplaceShort() (gas: 18628) -LibStringTest:testStringRuneCount() (gas: 2975992) -LibStringTest:testStringRuneCountDifferential(string) (runs: 256, μ: 6043, ~: 5821) +LibStringTest:testStringRuneCount() (gas: 3024248) +LibStringTest:testStringRuneCountDifferential(string) (runs: 257, μ: 6031, ~: 5810) LibStringTest:testStringSlice() (gas: 17049) -LibStringTest:testStringSlice(uint256) (runs: 256, μ: 682270, ~: 618818) +LibStringTest:testStringSlice(uint256) (runs: 257, μ: 753718, ~: 617411) LibStringTest:testStringSplit() (gas: 19683) -LibStringTest:testStringSplit(uint256) (runs: 256, μ: 718602, ~: 617835) +LibStringTest:testStringSplit(uint256) (runs: 257, μ: 717316, ~: 622500) LibStringTest:testStringStartsWith() (gas: 2558) -LibStringTest:testStringStartsWith(uint256) (runs: 256, μ: 708358, ~: 605784) -LibStringTest:testStringUpperDifferential() (gas: 3549675) -LibStringTest:testStringUpperDifferential(string) (runs: 256, μ: 8874, ~: 8507) +LibStringTest:testStringStartsWith(uint256) (runs: 257, μ: 746754, ~: 609420) +LibStringTest:testStringUpperDifferential() (gas: 3392177) +LibStringTest:testStringUpperDifferential(string) (runs: 257, μ: 8858, ~: 8507) LibStringTest:testStringUpperOriginal() (gas: 1769) LibStringTest:testToHexStringFixedLengthInsufficientLength() (gas: 3390) LibStringTest:testToHexStringFixedLengthPositiveNumberLong() (gas: 4446) LibStringTest:testToHexStringFixedLengthPositiveNumberShort() (gas: 1494) LibStringTest:testToHexStringFixedLengthUint256Max() (gas: 4491) -LibStringTest:testToHexStringFixedLengthZeroRightPadded(uint256,uint256) (runs: 256, μ: 8101, ~: 4897) +LibStringTest:testToHexStringFixedLengthZeroRightPadded(uint256,uint256) (runs: 257, μ: 7929, ~: 5508) LibStringTest:testToHexStringPositiveNumber() (gas: 1399) LibStringTest:testToHexStringUint256Max() (gas: 4210) LibStringTest:testToHexStringZero() (gas: 1336) -LibStringTest:testToHexStringZeroRightPadded(uint256) (runs: 256, μ: 2020, ~: 1337) +LibStringTest:testToHexStringZeroRightPadded(uint256) (runs: 257, μ: 1943, ~: 1420) LibStringTest:testToMinimalHexStringNoPrefixPositiveNumber() (gas: 5959) LibStringTest:testToMinimalHexStringNoPrefixUint256Max() (gas: 4054) LibStringTest:testToMinimalHexStringNoPrefixZero() (gas: 1340) -LibStringTest:testToMinimalHexStringNoPrefixZeroRightPadded(uint256) (runs: 256, μ: 2011, ~: 1335) +LibStringTest:testToMinimalHexStringNoPrefixZeroRightPadded(uint256) (runs: 257, μ: 1940, ~: 1418) LibStringTest:testToMinimalHexStringPositiveNumber() (gas: 6092) LibStringTest:testToMinimalHexStringUint256Max() (gas: 4237) LibStringTest:testToMinimalHexStringZero() (gas: 1383) -LibStringTest:testToMinimalHexStringZeroRightPadded(uint256) (runs: 256, μ: 2044, ~: 1361) +LibStringTest:testToMinimalHexStringZeroRightPadded(uint256) (runs: 257, μ: 1968, ~: 1444) LibStringTest:testToSmallString() (gas: 4420) LibStringTest:testToStringPositiveNumber() (gas: 1461) -LibStringTest:testToStringPositiveNumberBrutalized() (gas: 1444820) -LibStringTest:testToStringSignedDifferential(int256) (runs: 256, μ: 649797, ~: 574779) +LibStringTest:testToStringPositiveNumberBrutalized() (gas: 577888) +LibStringTest:testToStringSignedDifferential(int256) (runs: 257, μ: 769568, ~: 581076) LibStringTest:testToStringSignedGas() (gas: 7212) -LibStringTest:testToStringSignedMemory(int256) (runs: 256, μ: 709754, ~: 573844) +LibStringTest:testToStringSignedMemory(int256) (runs: 257, μ: 787297, ~: 578799) LibStringTest:testToStringSignedOriginalGas() (gas: 9760) LibStringTest:testToStringUint256Max() (gas: 7432) -LibStringTest:testToStringUint256MaxBrutalized() (gas: 586571) +LibStringTest:testToStringUint256MaxBrutalized() (gas: 590215) LibStringTest:testToStringZero() (gas: 1186) -LibStringTest:testToStringZeroBrutalized() (gas: 573986) -LibStringTest:testToStringZeroRightPadded(uint256) (runs: 256, μ: 689385, ~: 573834) -LibStringTest:test__codesize() (gas: 42004) -LibTTest:testLibT(bytes32,uint256) (runs: 256, μ: 1003, ~: 1003) +LibStringTest:testToStringZeroBrutalized() (gas: 577629) +LibStringTest:testToStringZeroRightPadded(uint256) (runs: 257, μ: 687580, ~: 577653) +LibStringTest:test__codesize() (gas: 42154) +LibTTest:testLibT(bytes32,uint256) (runs: 257, μ: 1003, ~: 1003) LibTTest:test__codesize() (gas: 1805) ReentrancyGuardTest:testRecursiveDirectUnguardedCall() (gas: 32203) ReentrancyGuardTest:testRecursiveIndirectUnguardedCall() (gas: 45600) diff --git a/test/Extcode.t.sol b/test/Extcode.t.sol new file mode 100644 index 0000000..e001fa8 --- /dev/null +++ b/test/Extcode.t.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "./utils/SoladyTest.sol"; + +contract ExtcodeTest is SoladyTest { + address internal _extcodecopy; + address internal _extcodesize; + address internal _extcodehash; + + function setUp() public { + _extcodecopy = address(uint160(uint256(keccak256("extcodecopy")))); + _extcodehash = address(uint160(uint256(keccak256("extcodehash")))); + _extcodesize = address(uint160(uint256(keccak256("extcodesize")))); + vm.etch(_extcodecopy, hex"604035806020353d3d353c3df3"); + vm.etch(_extcodesize, hex"3d353b3d52593df3"); + vm.etch(_extcodehash, hex"3d353f3d52593df3"); + } + + function testExtcodeInitCodes() public { + bytes memory extcodecopyInitCode = hex"6c604035806020353d3d353c3df33d52600d6013f3"; + _testInitCode(extcodecopyInitCode, _extcodecopy); + emit LogBytes32("extcodecopyInitCodeHash", keccak256(extcodecopyInitCode)); + + bytes memory extcodesizeInitCode = hex"673d353b3d52593df33d5260086018f3"; + _testInitCode(extcodesizeInitCode, _extcodesize); + emit LogBytes32("extcodesizeInitCodeHash", keccak256(extcodesizeInitCode)); + + bytes memory extcodehashInitCode = hex"673d353f3d52593df33d5260086018f3"; + _testInitCode(extcodehashInitCode, _extcodehash); + emit LogBytes32("extcodehashInitCodeHash", keccak256(extcodehashInitCode)); + } + + function _testInitCode(bytes memory initCode, address etched) internal { + address instance; + /// @solidity memory-safe-assembly + assembly { + instance := create(0, add(0x20, initCode), mload(initCode)) + } + assertEq(instance.code, etched.code); + } + + function testExtcodehash() public { + bytes32 expected; + /// @solidity memory-safe-assembly + assembly { + expected := extcodehash(address()) + } + (bool success, bytes memory result) = _extcodehash.call(abi.encode(address(this))); + assertEq(abi.decode(result, (bytes32)), expected); + assertTrue(success); + } + + function testExtcodesize() public { + bytes32 expected; + /// @solidity memory-safe-assembly + assembly { + expected := extcodesize(address()) + } + (bool success, bytes memory result) = _extcodesize.call(abi.encode(address(this))); + assertEq(abi.decode(result, (bytes32)), expected); + assertTrue(success); + } + + function testExtcodecopy(uint256 offset, uint256 length) public { + offset = _bound(offset, 0, 0xff); + length = _bound(length, 0, 0xffff); + (bool success, bytes memory result) = + _extcodecopy.call(abi.encode(address(this), offset, length)); + bytes memory expected; + /// @solidity memory-safe-assembly + assembly { + expected := mload(0x40) + mstore(expected, length) + extcodecopy(address(), add(expected, 0x20), offset, length) + mstore(0x40, add(add(expected, 0x20), length)) + } + assertEq(result.length, length); + assertTrue(success); + } +} diff --git a/test/utils/Brutalizer.sol b/test/utils/Brutalizer.sol new file mode 100644 index 0000000..ed0e28e --- /dev/null +++ b/test/utils/Brutalizer.sol @@ -0,0 +1,839 @@ +// 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 Multiplier for a mulmod Lehmer psuedorandom number generator. + /// Prime, and a primitive root of `_LPRNG_MODULO`. + uint256 private constant _LPRNG_MULTIPLIER = 0x100000000000000000000000000000051; + + /// @dev Modulo for a mulmod Lehmer psuedorandom number generator. (prime) + uint256 private constant _LPRNG_MODULO = + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff43; + + /// @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(add(offset, 0x20), zero, calldatasize()) + mstore(offset, add(caller(), gas())) + + // Fill the 64 bytes of scratch space with garbage. + let r := keccak256(offset, add(calldatasize(), 0x40)) + mstore(zero, r) + mstore(0x20, keccak256(zero, 0x40)) + r := mulmod(mload(0x10), _LPRNG_MULTIPLIER, _LPRNG_MODULO) + + 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(r, 0xf00000000)), and(shr(64, r), 0xfffff))) + + // Fill the free memory with garbage. + // prettier-ignore + for { let w := not(0) } 1 {} { + mstore(offset, mload(0x00)) + mstore(add(offset, 0x20), mload(0x20)) + 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), 0x00, start) + offset := add(offset, cSize) + times := add(times, w) // `sub(times, 1)`. + if iszero(times) { break } + } + // With a 1/16 chance, copy the contract's code to the scratch space. + if iszero(and(0xf00, r)) { + codecopy(0x00, mod(shr(128, r), add(codesize(), codesize())), 0x40) + mstore8(and(r, 0x3f), iszero(and(0x100000, r))) + } + } + } + + /// @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(add(offset, 0x20), zero, calldatasize()) + mstore(offset, add(caller(), gas())) + + // Fill the 64 bytes of scratch space with garbage. + let r := keccak256(offset, add(calldatasize(), 0x40)) + mstore(zero, r) + mstore(0x20, keccak256(zero, 0x40)) + r := mulmod(mload(0x10), _LPRNG_MULTIPLIER, _LPRNG_MODULO) + if iszero(and(0xf00, r)) { + codecopy(0x00, mod(shr(128, r), add(codesize(), codesize())), 0x40) + mstore8(and(r, 0x3f), iszero(and(0x100000, r))) + } + } + } + + /// @dev Fills the lower memory with junk, for more robust testing of inline assembly + /// which reads/write to the memory. + /// For efficiency, this only fills a small portion of the free memory. + function _brutalizeLowerMemory() 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(add(offset, 0x20), zero, calldatasize()) + mstore(offset, add(caller(), gas())) + + // Fill the 64 bytes of scratch space with garbage. + let r := keccak256(offset, add(calldatasize(), 0x40)) + mstore(zero, r) + mstore(0x20, keccak256(zero, 0x40)) + r := mulmod(mload(0x10), _LPRNG_MULTIPLIER, _LPRNG_MODULO) + + for {} 1 {} { + if iszero(and(0x7000, r)) { + let x := keccak256(zero, 0x40) + mstore(offset, x) + mstore(add(0x20, offset), x) + mstore(add(0x40, offset), x) + mstore(add(0x60, offset), x) + mstore(add(0x80, offset), x) + mstore(add(0xa0, offset), x) + mstore(add(0xc0, offset), x) + mstore(add(0xe0, offset), x) + mstore(add(0x100, offset), x) + mstore(add(0x120, offset), x) + mstore(add(0x140, offset), x) + mstore(add(0x160, offset), x) + mstore(add(0x180, offset), x) + mstore(add(0x1a0, offset), x) + mstore(add(0x1c0, offset), x) + mstore(add(0x1e0, offset), x) + mstore(add(0x200, offset), x) + mstore(add(0x220, offset), x) + mstore(add(0x240, offset), x) + mstore(add(0x260, offset), x) + break + } + codecopy(offset, byte(0, r), codesize()) + break + } + if iszero(and(0x300, r)) { + codecopy(0x00, mod(shr(128, r), add(codesize(), codesize())), 0x40) + mstore8(and(r, 0x3f), iszero(and(0x100000, r))) + } + } + } + + /// @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 Fills the lower memory with junk, for more robust testing of inline assembly + /// which reads/write to the memory. + modifier brutalizeLowerMemory() { + _brutalizeLowerMemory(); + _; + _checkMemory(); + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalized(address value) internal pure returns (address result) { + uint256 r = uint256(uint160(value)); + r = (__brutalizerRandomness(r) << 160) ^ r; + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint8(uint8 value) internal pure returns (uint8 result) { + uint256 r = (__brutalizerRandomness(value) << 8) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes1(bytes1 value) internal pure returns (bytes1 result) { + bytes32 r = __brutalizedBytesN(value, 8); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint16(uint16 value) internal pure returns (uint16 result) { + uint256 r = (__brutalizerRandomness(value) << 16) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes2(bytes2 value) internal pure returns (bytes2 result) { + bytes32 r = __brutalizedBytesN(value, 16); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint24(uint24 value) internal pure returns (uint24 result) { + uint256 r = (__brutalizerRandomness(value) << 24) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes3(bytes3 value) internal pure returns (bytes3 result) { + bytes32 r = __brutalizedBytesN(value, 24); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint32(uint32 value) internal pure returns (uint32 result) { + uint256 r = (__brutalizerRandomness(value) << 32) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes4(bytes4 value) internal pure returns (bytes4 result) { + bytes32 r = __brutalizedBytesN(value, 32); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint40(uint40 value) internal pure returns (uint40 result) { + uint256 r = (__brutalizerRandomness(value) << 40) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes5(bytes5 value) internal pure returns (bytes5 result) { + bytes32 r = __brutalizedBytesN(value, 40); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint48(uint48 value) internal pure returns (uint48 result) { + uint256 r = (__brutalizerRandomness(value) << 48) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes6(bytes6 value) internal pure returns (bytes6 result) { + bytes32 r = __brutalizedBytesN(value, 48); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint56(uint56 value) internal pure returns (uint56 result) { + uint256 r = (__brutalizerRandomness(value) << 56) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes7(bytes7 value) internal pure returns (bytes7 result) { + bytes32 r = __brutalizedBytesN(value, 56); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint64(uint64 value) internal pure returns (uint64 result) { + uint256 r = (__brutalizerRandomness(value) << 64) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes8(bytes8 value) internal pure returns (bytes8 result) { + bytes32 r = __brutalizedBytesN(value, 64); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint72(uint72 value) internal pure returns (uint72 result) { + uint256 r = (__brutalizerRandomness(value) << 72) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes9(bytes9 value) internal pure returns (bytes9 result) { + bytes32 r = __brutalizedBytesN(value, 72); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint80(uint80 value) internal pure returns (uint80 result) { + uint256 r = (__brutalizerRandomness(value) << 80) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes10(bytes10 value) internal pure returns (bytes10 result) { + bytes32 r = __brutalizedBytesN(value, 80); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint88(uint88 value) internal pure returns (uint88 result) { + uint256 r = (__brutalizerRandomness(value) << 88) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes11(bytes11 value) internal pure returns (bytes11 result) { + bytes32 r = __brutalizedBytesN(value, 88); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint96(uint96 value) internal pure returns (uint96 result) { + uint256 r = (__brutalizerRandomness(value) << 96) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes12(bytes12 value) internal pure returns (bytes12 result) { + bytes32 r = __brutalizedBytesN(value, 96); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint104(uint104 value) internal pure returns (uint104 result) { + uint256 r = (__brutalizerRandomness(value) << 104) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes13(bytes13 value) internal pure returns (bytes13 result) { + bytes32 r = __brutalizedBytesN(value, 104); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint112(uint112 value) internal pure returns (uint112 result) { + uint256 r = (__brutalizerRandomness(value) << 112) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes14(bytes14 value) internal pure returns (bytes14 result) { + bytes32 r = __brutalizedBytesN(value, 112); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint120(uint120 value) internal pure returns (uint120 result) { + uint256 r = (__brutalizerRandomness(value) << 120) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes15(bytes15 value) internal pure returns (bytes15 result) { + bytes32 r = __brutalizedBytesN(value, 120); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint128(uint128 value) internal pure returns (uint128 result) { + uint256 r = (__brutalizerRandomness(value) << 128) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes16(bytes16 value) internal pure returns (bytes16 result) { + bytes32 r = __brutalizedBytesN(value, 128); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint136(uint136 value) internal pure returns (uint136 result) { + uint256 r = (__brutalizerRandomness(value) << 136) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes17(bytes17 value) internal pure returns (bytes17 result) { + bytes32 r = __brutalizedBytesN(value, 136); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint144(uint144 value) internal pure returns (uint144 result) { + uint256 r = (__brutalizerRandomness(value) << 144) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes18(bytes18 value) internal pure returns (bytes18 result) { + bytes32 r = __brutalizedBytesN(value, 144); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint152(uint152 value) internal pure returns (uint152 result) { + uint256 r = (__brutalizerRandomness(value) << 152) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes19(bytes19 value) internal pure returns (bytes19 result) { + bytes32 r = __brutalizedBytesN(value, 152); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint160(uint160 value) internal pure returns (uint160 result) { + uint256 r = (__brutalizerRandomness(value) << 160) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes20(bytes20 value) internal pure returns (bytes20 result) { + bytes32 r = __brutalizedBytesN(value, 160); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint168(uint168 value) internal pure returns (uint168 result) { + uint256 r = (__brutalizerRandomness(value) << 168) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes21(bytes21 value) internal pure returns (bytes21 result) { + bytes32 r = __brutalizedBytesN(value, 168); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint176(uint176 value) internal pure returns (uint176 result) { + uint256 r = (__brutalizerRandomness(value) << 176) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes22(bytes22 value) internal pure returns (bytes22 result) { + bytes32 r = __brutalizedBytesN(value, 176); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint184(uint184 value) internal pure returns (uint184 result) { + uint256 r = (__brutalizerRandomness(value) << 184) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes23(bytes23 value) internal pure returns (bytes23 result) { + bytes32 r = __brutalizedBytesN(value, 184); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint192(uint192 value) internal pure returns (uint192 result) { + uint256 r = (__brutalizerRandomness(value) << 192) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes24(bytes24 value) internal pure returns (bytes24 result) { + bytes32 r = __brutalizedBytesN(value, 192); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint200(uint200 value) internal pure returns (uint200 result) { + uint256 r = (__brutalizerRandomness(value) << 200) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes25(bytes25 value) internal pure returns (bytes25 result) { + bytes32 r = __brutalizedBytesN(value, 200); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint208(uint208 value) internal pure returns (uint208 result) { + uint256 r = (__brutalizerRandomness(value) << 208) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes26(bytes26 value) internal pure returns (bytes26 result) { + bytes32 r = __brutalizedBytesN(value, 208); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint216(uint216 value) internal pure returns (uint216 result) { + uint256 r = (__brutalizerRandomness(value) << 216) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes27(bytes27 value) internal pure returns (bytes27 result) { + bytes32 r = __brutalizedBytesN(value, 216); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint224(uint224 value) internal pure returns (uint224 result) { + uint256 r = (__brutalizerRandomness(value) << 224) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes28(bytes28 value) internal pure returns (bytes28 result) { + bytes32 r = __brutalizedBytesN(value, 224); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint232(uint232 value) internal pure returns (uint232 result) { + uint256 r = (__brutalizerRandomness(value) << 232) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes29(bytes29 value) internal pure returns (bytes29 result) { + bytes32 r = __brutalizedBytesN(value, 232); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint240(uint240 value) internal pure returns (uint240 result) { + uint256 r = (__brutalizerRandomness(value) << 240) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes30(bytes30 value) internal pure returns (bytes30 result) { + bytes32 r = __brutalizedBytesN(value, 240); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint248(uint248 value) internal pure returns (uint248 result) { + uint256 r = (__brutalizerRandomness(value) << 248) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes31(bytes31 value) internal pure returns (bytes31 result) { + bytes32 r = __brutalizedBytesN(value, 248); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalized(bool value) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + calldatacopy(result, 0x00, calldatasize()) + mstore(0x20, keccak256(result, calldatasize())) + mstore(0x10, xor(value, mload(0x10))) + let r := keccak256(0x00, 0x88) + mstore(0x10, r) + result := mul(iszero(iszero(value)), r) + if iszero(and(1, shr(128, mulmod(r, _LPRNG_MULTIPLIER, _LPRNG_MODULO)))) { + result := iszero(iszero(result)) + } + } + } + + /// @dev Returns a brutalizer randomness. + function __brutalizedBytesN(bytes32 x, uint256 s) private pure returns (bytes32) { + return bytes32(uint256((__brutalizerRandomness(uint256(x)) >> s) ^ uint256(x))); + } + + /// @dev Returns a brutalizer randomness. + function __brutalizerRandomness(uint256 seed) private pure returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + calldatacopy(result, 0x00, calldatasize()) + mstore(0x20, keccak256(result, calldatasize())) + mstore(0x10, xor(seed, mload(0x10))) + result := keccak256(0x00, 0x88) + mstore(0x10, result) + if iszero(and(7, shr(128, mulmod(result, _LPRNG_MULTIPLIER, _LPRNG_MODULO)))) { + result := 0 + } + } + } + + /// @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..ea13e1d 100644 --- a/test/utils/TestPlus.sol +++ b/test/utils/TestPlus.sol @@ -1,253 +1,568 @@ // 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); + + event LogStringArray(string name, string[] value); + event LogStringArray(string[] value); + event LogBytesArray(string name, bytes[] value); + event LogBytesArray(bytes[] value); + event LogUintArray(string name, uint256[] value); + event LogUintArray(uint256[] value); + event LogBytes32Array(string name, bytes32[] value); + event LogBytes32Array(bytes32[] value); + event LogIntArray(string name, int256[] value); + event LogIntArray(int256[] value); + event LogAddressArray(string name, address[] value); + event LogAddressArray(address[] value); + event LogBoolArray(string name, bool[] value); + event LogBoolArray(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()) + /// @dev This is the keccak256 of a very long string I randomly mashed on my keyboard. + uint256 private constant _TESTPLUS_RANDOMNESS_SLOT = + 0xd715531fe383f818c5f158c342925dcf01b954d24678ada4d07c36af0f20e1ee; - // 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 The maximum private key. + uint256 private constant _PRIVATE_KEY_MAX = + 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140; - let r0 := mload(zero) - let r1 := mload(0x20) + /// @dev Some constant to brutalize the upper bits of addresses. + uint256 private constant _ADDRESS_BRUTALIZER = 0xc0618c2bfd481dcf3e31738f; - 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 } + /// @dev Multiplier for a mulmod Lehmer psuedorandom number generator. + /// Prime, and a primitive root of `_LPRNG_MODULO`. + uint256 private constant _LPRNG_MULTIPLIER = 0x100000000000000000000000000000051; - // 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))) + /// @dev Modulo for a mulmod Lehmer psuedorandom number generator. (prime) + uint256 private constant _LPRNG_MODULO = + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff43; - // 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; + /// @dev Returns whether the `value` has been generated for `typeId` and `groupId` before. + function __markAsGenerated(bytes32 typeId, bytes32 groupId, uint256 value) + private + returns (bool isSet) + { /// @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 m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, value) + mstore(0x20, groupId) + mstore(0x40, typeId) + mstore(0x60, _TESTPLUS_RANDOMNESS_SLOT) + let s := keccak256(0x00, 0x80) + isSet := sload(s) + sstore(s, 1) + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero pointer. } } - /// @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`. - function _random() internal returns (uint256 r) { + /// This function may return a previously returned result. + function _random() internal returns (uint256 result) { /// @solidity memory-safe-assembly assembly { - // This is the keccak256 of a very long string I randomly mashed on my keyboard. - let sSlot := 0xd715531fe383f818c5f158c342925dcf01b954d24678ada4d07c36af0f20e1ee - let sValue := sload(sSlot) - + result := _TESTPLUS_RANDOMNESS_SLOT + let sValue := sload(result) mstore(0x20, sValue) - r := keccak256(0x20, 0x40) - + let r := keccak256(0x20, 0x40) // If the storage is uninitialized, initialize it to the keccak256 of the calldata. if iszero(sValue) { - sValue := sSlot - let m := mload(0x40) - calldatacopy(m, 0, calldatasize()) - r := keccak256(m, calldatasize()) + sValue := result + calldatacopy(mload(0x40), 0x00, calldatasize()) + r := keccak256(mload(0x40), calldatasize()) } - sstore(sSlot, add(r, 1)) + sstore(result, add(r, 1)) // Do some biased sampling for more robust tests. // prettier-ignore for {} 1 {} { - let d := byte(0, r) - // With a 1/256 chance, randomly set `r` to any of 0,1,2. - if iszero(d) { - r := and(r, 3) + let y := mulmod(r, _LPRNG_MULTIPLIER, _LPRNG_MODULO) + // With a 1/256 chance, randomly set `r` to any of 0,1,2,3. + if iszero(byte(19, y)) { + r := and(byte(11, y), 3) break } + let d := byte(17, y) // With a 1/2 chance, set `r` to near a random power of 2. if iszero(and(2, d)) { // Set `t` either `not(0)` or `xor(sValue, r)`. - let t := xor(not(0), mul(iszero(and(4, d)), not(xor(sValue, r)))) - // Set `r` to `t` shifted left or right by a random multiple of 8. - switch and(8, d) - case 0 { - if iszero(and(16, d)) { t := 1 } - r := add(shl(shl(3, and(byte(3, r), 0x1f)), t), sub(and(r, 7), 3)) - } - default { + let t := or(xor(sValue, r), sub(0, and(1, d))) + // Set `r` to `t` shifted left or right. + // prettier-ignore + for {} 1 {} { + if iszero(and(8, d)) { + if iszero(and(16, d)) { t := 1 } + if iszero(and(32, d)) { + r := add(shl(shl(3, and(byte(7, y), 31)), t), sub(3, and(7, r))) + break + } + r := add(shl(byte(7, y), t), sub(511, and(1023, r))) + break + } if iszero(and(16, d)) { t := shl(255, 1) } - r := add(shr(shl(3, and(byte(3, r), 0x1f)), t), sub(and(r, 7), 3)) + if iszero(and(32, d)) { + r := add(shr(shl(3, and(byte(7, y), 31)), t), sub(3, and(7, r))) + break + } + r := add(shr(byte(7, y), t), sub(511, and(1023, r))) + break } // With a 1/2 chance, negate `r`. - if iszero(and(0x20, d)) { r := not(r) } + r := xor(sub(0, shr(7, d)), r) break } // Otherwise, just set `r` to `xor(sValue, r)`. r := xor(sValue, r) break } + result := r } } - /// @dev Returns a random signer and its private key. - function _randomSigner() internal returns (address signer, uint256 privateKey) { - uint256 privateKeyMax = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140; - privateKey = _hem(_random(), 1, privateKeyMax); + /// @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`. + function _randomUnique(uint256 groupId) internal returns (uint256 result) { + result = _randomUnique(bytes32(groupId)); + } + + /// @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`. + function _randomUnique(bytes32 groupId) internal returns (uint256 result) { + do { + result = _random(); + } while (__markAsGenerated("uint256", groupId, result)); + } + + /// @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`. + function _randomUnique() internal returns (uint256 result) { + result = _randomUnique(""); + } + + /// @dev Returns a pseudorandom number, uniformly distributed in [0 .. 2**256 - 1] (inclusive). + function _randomUniform() internal returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + result := _TESTPLUS_RANDOMNESS_SLOT + // prettier-ignore + for { let sValue := sload(result) } 1 {} { + // If the storage is uninitialized, initialize it to the keccak256 of the calldata. + if iszero(sValue) { + calldatacopy(mload(0x40), 0x00, calldatasize()) + sValue := keccak256(mload(0x40), calldatasize()) + sstore(result, sValue) + result := sValue + break + } + mstore(0x1f, sValue) + sValue := keccak256(0x20, 0x40) + sstore(result, sValue) + result := sValue + break + } + } + } + + /// @dev Returns a boolean with an approximately 1/n chance of being true. + /// This function may return a previously returned result. + function _randomChance(uint256 n) internal returns (bool result) { + uint256 r = _randomUniform(); + /// @solidity memory-safe-assembly + assembly { + result := iszero(mod(r, n)) + } + } + + /// @dev Returns a random private key that can be used for ECDSA signing. + /// This function may return a previously returned result. + function _randomPrivateKey() internal returns (uint256 result) { + result = _randomUniform(); + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + if iszero(and(result, 0x10)) { + if iszero(and(result, 0x20)) { + result := add(and(result, 0xf), 1) + break + } + result := sub(_PRIVATE_KEY_MAX, and(result, 0xf)) + break + } + result := shr(1, result) + break + } + } + } + + /// @dev Returns a random private key that can be used for ECDSA signing. + function _randomUniquePrivateKey(uint256 groupId) internal returns (uint256 result) { + result = _randomUniquePrivateKey(bytes32(groupId)); + } + + /// @dev Returns a random private key that can be used for ECDSA signing. + function _randomUniquePrivateKey(bytes32 groupId) internal returns (uint256 result) { + do { + result = _randomPrivateKey(); + } while (__markAsGenerated("uint256", groupId, result)); + } + + /// @dev Returns a random private key that can be used for ECDSA signing. + function _randomUniquePrivateKey() internal returns (uint256 result) { + result = _randomUniquePrivateKey(""); + } + + /// @dev Private helper function to get the signer from a private key. + function __getSigner(uint256 privateKey) private view returns (uint256 result) { /// @solidity memory-safe-assembly assembly { mstore(0x00, 0xffa18649) // `addr(uint256)`. mstore(0x20, privateKey) - if iszero(call(gas(), _VM_ADDRESS, 0, 0x1c, 0x24, 0x00, 0x20)) { revert(0, 0) } - signer := mload(0x00) + result := mload(staticcall(gas(), _VM_ADDRESS, 0x1c, 0x24, 0x01, 0x20)) } } - /// @dev Returns a random address. + /// @dev Private helper to ensure an address is brutalized. + function __toBrutalizedAddress(address a) private pure returns (address result) { + /// @solidity memory-safe-assembly + assembly { + result := keccak256(0x00, 0x88) + result := xor(shl(160, xor(result, _ADDRESS_BRUTALIZER)), a) + mstore(0x10, result) + } + } + + /// @dev Private helper to ensure an address is brutalized. + function __toBrutalizedAddress(uint256 a) private pure returns (address result) { + /// @solidity memory-safe-assembly + assembly { + result := keccak256(0x00, 0x88) + result := xor(shl(160, xor(result, _ADDRESS_BRUTALIZER)), a) + mstore(0x10, result) + } + } + + /// @dev Returns a pseudorandom signer and its private key. + /// This function may return a previously returned result. + /// The signer may have dirty upper 96 bits. + function _randomSigner() internal returns (address signer, uint256 privateKey) { + privateKey = _randomPrivateKey(); + signer = __toBrutalizedAddress(__getSigner(privateKey)); + } + + /// @dev Returns a pseudorandom signer and its private key. + /// The signer may have dirty upper 96 bits. + function _randomUniqueSigner(uint256 groupId) + internal + returns (address signer, uint256 privateKey) + { + (signer, privateKey) = _randomUniqueSigner(bytes32(groupId)); + } + + /// @dev Returns a pseudorandom signer and its private key. + /// The signer may have dirty upper 96 bits. + function _randomUniqueSigner(bytes32 groupId) + internal + returns (address signer, uint256 privateKey) + { + privateKey = _randomUniquePrivateKey(groupId); + signer = __toBrutalizedAddress(__getSigner(privateKey)); + } + + /// @dev Returns a pseudorandom signer and its private key. + /// The signer may have dirty upper 96 bits. + function _randomUniqueSigner() internal returns (address signer, uint256 privateKey) { + (signer, privateKey) = _randomUniqueSigner(""); + } + + /// @dev Returns a pseudorandom address. + /// The result may have dirty upper 96 bits. + /// This function will not return an existing contract. + /// This function may return a previously returned result. function _randomAddress() internal returns (address result) { - result = address(uint160(_random())); + uint256 r = _randomUniform(); + /// @solidity memory-safe-assembly + assembly { + result := xor(shl(158, r), and(sub(7, shr(252, r)), r)) + } } - /// @dev Returns a random non-zero address. - function _randomNonZeroAddress() internal returns (address result) { + /// @dev Returns a pseudorandom address. + /// The result may have dirty upper 96 bits. + /// This function will not return an existing contract. + function _randomUniqueAddress(uint256 groupId) internal returns (address result) { + result = _randomUniqueAddress(bytes32(groupId)); + } + + /// @dev Returns a pseudorandom address. + /// The result may have dirty upper 96 bits. + /// This function will not return an existing contract. + function _randomUniqueAddress(bytes32 groupId) internal returns (address result) { do { - result = address(uint160(_random())); - } while (result == address(0)); + result = _randomAddress(); + } while (__markAsGenerated("address", groupId, uint160(result))); } - /// @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; + /// @dev Returns a pseudorandom address. + /// The result may have dirty upper 96 bits. + /// This function will not return an existing contract. + function _randomUniqueAddress() internal returns (address result) { + result = _randomUniqueAddress(""); + } + + /// @dev Returns a pseudorandom non-zero address. + /// The result may have dirty upper 96 bits. + /// This function will not return an existing contract. + /// This function may return a previously returned result. + function _randomNonZeroAddress() internal returns (address result) { + uint256 r = _randomUniform(); /// @solidity memory-safe-assembly assembly { - mstore(twoWords, and(add(mload(twoWords), 0x1f), not(0x1f))) + result := xor(shl(158, r), and(sub(7, shr(252, r)), r)) + if iszero(shl(96, result)) { + mstore(0x00, result) + result := keccak256(0x00, 0x30) + } } } - /// @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; + /// @dev Returns a pseudorandom non-zero address. + /// The result may have dirty upper 96 bits. + /// This function will not return an existing contract. + function _randomUniqueNonZeroAddress(uint256 groupId) internal returns (address result) { + result = _randomUniqueNonZeroAddress(bytes32(groupId)); + } + + /// @dev Returns a pseudorandom non-zero address. + /// The result may have dirty upper 96 bits. + /// This function will not return an existing contract. + function _randomUniqueNonZeroAddress(bytes32 groupId) internal returns (address result) { + do { + result = _randomNonZeroAddress(); + } while (__markAsGenerated("address", groupId, uint160(result))); + } + + /// @dev Returns a pseudorandom non-zero address. + /// The result may have dirty upper 96 bits. + /// This function will not return an existing contract. + function _randomUniqueNonZeroAddress() internal returns (address result) { + result = _randomUniqueNonZeroAddress(""); + } + + /// @dev Cleans the upper 96 bits of the address. + /// This is included so that CI passes for older solc versions with --via-ir. + function _cleaned(address a) internal pure returns (address result) { /// @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) + result := shr(96, shl(96, a)) + } + } + + /// @dev Returns a pseudorandom address. + /// The result may have dirty upper 96 bits. + /// This function may return a previously returned result. + function _randomAddressWithVmVars() internal returns (address result) { + if (_randomChance(8)) result = __toBrutalizedAddress(_randomVmVar()); + else result = _randomAddress(); + } + + /// @dev Returns a pseudorandom non-zero address. + /// The result may have dirty upper 96 bits. + /// This function may return a previously returned result. + function _randomNonZeroAddressWithVmVars() internal returns (address result) { + do { + if (_randomChance(8)) result = __toBrutalizedAddress(_randomVmVar()); + else result = _randomAddress(); + } while (result == address(0)); + } + + /// @dev Returns a random variable in the virtual machine. + function _randomVmVar() internal returns (uint256 result) { + uint256 r = _randomUniform(); + uint256 t = r % 11; + if (t <= 4) { + if (t == 0) return uint160(address(this)); + if (t == 1) return uint160(tx.origin); + if (t == 2) return uint160(msg.sender); + if (t == 3) return uint160(_VM_ADDRESS); + if (t == 4) return uint160(0x000000000000000000636F6e736F6c652e6c6f67); + } + uint256 y = r >> 32; + if (t == 5) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, r) + codecopy(0x00, mod(and(y, 0xffff), add(codesize(), 0x20)), 0x20) + result := mload(0x00) + } + return result; + } + if (t == 6) { + /// @solidity memory-safe-assembly + assembly { + calldatacopy(0x00, mod(and(y, 0xffff), add(calldatasize(), 0x20)), 0x20) + result := mload(0x00) + } + return result; + } + if (t == 7) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + returndatacopy(m, 0x00, returndatasize()) + result := mload(add(m, mod(and(y, 0xffff), add(returndatasize(), 0x20)))) + } + return result; + } + if (t == 8) { + /// @solidity memory-safe-assembly + assembly { + result := sload(and(y, 0xff)) + } + return result; + } + if (t == 9) { + /// @solidity memory-safe-assembly + assembly { + result := mload(mod(y, add(mload(0x40), 0x40))) + } + return result; } + result = __getSigner(_randomPrivateKey()); } - /// @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; + /// @dev Returns a pseudorandom hashed address. + /// The result may have dirty upper 96 bits. + /// This function will not return an existing contract. + /// This function will not return a precompile address. + /// This function will not return a zero address. + /// This function may return a previously returned result. + function _randomHashedAddress() internal returns (address result) { + uint256 r = _randomUniform(); /// @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) + mstore(0x1f, and(sub(7, shr(252, r)), r)) + calldatacopy(0x00, 0x00, 0x24) + result := keccak256(0x00, 0x3f) } - 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; + /// @dev Returns a pseudorandom address. + function _randomUniqueHashedAddress(uint256 groupId) internal returns (address result) { + result = _randomUniqueHashedAddress(bytes32(groupId)); + } + + /// @dev Returns a pseudorandom address. + function _randomUniqueHashedAddress(bytes32 groupId) internal returns (address result) { + do { + result = _randomHashedAddress(); + } while (__markAsGenerated("address", groupId, uint160(result))); + } + + /// @dev Returns a pseudorandom address. + function _randomUniqueHashedAddress() internal returns (address result) { + result = _randomUniqueHashedAddress(""); + } + + /// @dev Private helper function for returning random bytes. + function __randomBytes(bool zeroRightPad) private returns (bytes memory result) { + uint256 r = _randomUniform(); /// @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 } } + let n := and(r, 0x1ffff) + let t := shr(24, r) + for {} 1 {} { + // With a 1/256 chance, just return the zero pointer as the result. + if iszero(and(t, 0xff0)) { + result := 0x60 + break + } + result := mload(0x40) + // With a 15/16 chance, set the length to be + // exponentially distributed in the range [0,255] (inclusive). + if shr(252, r) { n := shr(and(t, 0x7), byte(5, r)) } + // Store some fixed word at the start of the string. + // We want this function to sometimes return duplicates. + mstore(add(result, 0x20), xor(calldataload(0x00), _TESTPLUS_RANDOMNESS_SLOT)) + // With a 1/2 chance, copy the contract code to the start and end. + if iszero(and(t, 0x1000)) { + // Copy to the start. + if iszero(and(t, 0x2000)) { codecopy(result, byte(1, r), codesize()) } + // Copy to the end. + codecopy(add(result, n), byte(2, r), 0x40) + } + // With a 1/16 chance, randomize the start and end. + if iszero(and(t, 0xf0000)) { + let y := mulmod(r, _LPRNG_MULTIPLIER, _LPRNG_MODULO) + mstore(add(result, 0x20), y) + mstore(add(result, n), xor(r, y)) + } + // With a 1/256 chance, make the result entirely zero bytes. + if iszero(byte(4, r)) { codecopy(result, codesize(), add(n, 0x20)) } + // Skip the zero-right-padding if not required. + if iszero(zeroRightPad) { + mstore(0x40, add(n, add(0x40, result))) // Allocate memory. + mstore(result, n) // Store the length. + break + } + mstore(add(add(result, 0x20), n), 0) // Zeroize the word after the result. + mstore(0x40, add(n, add(0x60, result))) // Allocate memory. + mstore(result, n) // Store the length. + break + } } - 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 Returns a random bytes string from 0 to 131071 bytes long. + /// This random bytes string may NOT be zero-right-padded. + /// This is intentional for memory robustness testing. + /// This function may return a previously returned result. + function _randomBytes() internal returns (bytes memory result) { + result = __randomBytes(false); + } + + /// @dev Returns a random bytes string from 0 to 131071 bytes long. + /// This function may return a previously returned result. + function _randomBytesZeroRightPadded() internal returns (bytes memory result) { + result = __randomBytes(true); + } + + /// @dev Truncate the bytes to `n` bytes. + /// Returns the result for function chaining. + function _truncateBytes(bytes memory b, uint256 n) + internal + pure + returns (bytes memory result) + { + /// @solidity memory-safe-assembly + assembly { + if gt(mload(b), n) { mstore(b, n) } + result := b + } } /// @dev Adapted from `bound`: @@ -260,7 +575,6 @@ contract TestPlus { returns (uint256 result) { require(min <= max, "Max is less than min."); - /// @solidity memory-safe-assembly assembly { // prettier-ignore @@ -273,19 +587,15 @@ contract TestPlus { result := x break } - let size := add(sub(max, min), 1) - if and(iszero(gt(x, 3)), gt(size, x)) { + if lt(gt(x, 3), gt(size, x)) { result := add(min, x) break } - - let w := not(0) - if and(iszero(lt(x, sub(0, 4))), gt(size, sub(w, x))) { - result := sub(max, sub(w, x)) + if lt(lt(x, not(3)), gt(size, not(x))) { + result := sub(max, not(x)) break } - // Otherwise, wrap x into the range [min, max], // i.e. the range is inclusive. if iszero(lt(x, max)) { @@ -295,7 +605,7 @@ contract TestPlus { result := max break } - result := add(add(min, r), w) + result := sub(add(min, r), 1) break } let d := sub(min, x) @@ -336,9 +646,7 @@ contract TestPlus { for { let i := 0 } lt(i, n) { i := add(0x20, i) } { mstore(add(add(m, 0x80), i), mload(add(add(ic2fBytecode, 0x20), i))) } - if iszero(call(gas(), _VM_ADDRESS, 0, add(m, 0x1c), add(n, 0x64), 0x00, 0x00)) { - revert(0, 0) - } + pop(call(gas(), _VM_ADDRESS, 0, add(m, 0x1c), add(n, 0x64), 0x00, 0x00)) } } /// @solidity memory-safe-assembly From 8e607540c28b4b6401460a1d8b5df9fdce3d6c90 Mon Sep 17 00:00:00 2001 From: Vectorized Date: Fri, 23 Aug 2024 14:11:05 +0000 Subject: [PATCH 2/2] T --- .gas-snapshot | 10 +++++----- test/Extcode.t.sol | 22 +++++++++++++++++++--- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 9171dfe..2ef5dff 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,8 +1,8 @@ -ExtcodeTest:testExtcodeInitCodes() (gas: 129646) -ExtcodeTest:testExtcodecopy(uint256,uint256) (runs: 257, μ: 28153, ~: 11129) -ExtcodeTest:testExtcodehash() (gas: 5811) -ExtcodeTest:testExtcodesize() (gas: 5799) -ExtcodeTest:test__codesize() (gas: 4946) +ExtcodeTest:testExtcodeInitCodes() (gas: 181260) +ExtcodeTest:testExtcodecopy(uint256,uint256) (runs: 257, μ: 101359, ~: 100818) +ExtcodeTest:testExtcodehash() (gas: 5810) +ExtcodeTest:testExtcodesize() (gas: 11308) +ExtcodeTest:test__codesize() (gas: 5498) LibStringTest:testAddressToHexStringZeroRightPadded(address) (runs: 257, μ: 3384, ~: 3384) LibStringTest:testBytesToHexString() (gas: 6422) LibStringTest:testBytesToHexString(bytes) (runs: 257, μ: 686305, ~: 598437) diff --git a/test/Extcode.t.sol b/test/Extcode.t.sol index e001fa8..78027f3 100644 --- a/test/Extcode.t.sol +++ b/test/Extcode.t.sol @@ -7,17 +7,25 @@ contract ExtcodeTest is SoladyTest { address internal _extcodecopy; address internal _extcodesize; address internal _extcodehash; + address internal _extcode; function setUp() public { + _extcode = address(uint160(uint256(keccak256("extcode")))); _extcodecopy = address(uint160(uint256(keccak256("extcodecopy")))); _extcodehash = address(uint160(uint256(keccak256("extcodehash")))); _extcodesize = address(uint160(uint256(keccak256("extcodesize")))); + vm.etch(_extcode, hex"383618600e573d353b3d52593df35b6040358038353d3d353c803df300000000"); vm.etch(_extcodecopy, hex"604035806020353d3d353c3df3"); vm.etch(_extcodesize, hex"3d353b3d52593df3"); vm.etch(_extcodehash, hex"3d353f3d52593df3"); } function testExtcodeInitCodes() public { + bytes memory extcodeInitCode = + hex"7b383618600e573d353b3d52593df35b6040358038353d3d353c803df33d5260206004f3"; + _testInitCode(extcodeInitCode, _extcode); + emit LogBytes32("extcodeInitCodeHash", keccak256(extcodeInitCode)); + bytes memory extcodecopyInitCode = hex"6c604035806020353d3d353c3df33d52600d6013f3"; _testInitCode(extcodecopyInitCode, _extcodecopy); emit LogBytes32("extcodecopyInitCodeHash", keccak256(extcodecopyInitCode)); @@ -60,11 +68,15 @@ contract ExtcodeTest is SoladyTest { (bool success, bytes memory result) = _extcodesize.call(abi.encode(address(this))); assertEq(abi.decode(result, (bytes32)), expected); assertTrue(success); + + (success, result) = _extcode.call(abi.encode(address(this))); + assertEq(abi.decode(result, (bytes32)), expected); + assertTrue(success); } function testExtcodecopy(uint256 offset, uint256 length) public { offset = _bound(offset, 0, 0xff); - length = _bound(length, 0, 0xffff); + length = _bound(length, 0, 0x1ff); (bool success, bytes memory result) = _extcodecopy.call(abi.encode(address(this), offset, length)); bytes memory expected; @@ -73,9 +85,13 @@ contract ExtcodeTest is SoladyTest { expected := mload(0x40) mstore(expected, length) extcodecopy(address(), add(expected, 0x20), offset, length) - mstore(0x40, add(add(expected, 0x20), length)) + mstore(0x40, add(add(expected, 0x40), length)) } - assertEq(result.length, length); + assertEq(result, expected); + assertTrue(success); + + (success, result) = _extcode.call(abi.encode(address(this), offset, length)); + assertEq(result, expected); assertTrue(success); } }