Skip to content

Commit

Permalink
♻️ Refactor brutalizer for tests (#905)
Browse files Browse the repository at this point in the history
  • Loading branch information
Vectorized authored Apr 19, 2024
1 parent e6ad61c commit 58362f7
Show file tree
Hide file tree
Showing 17 changed files with 639 additions and 722 deletions.
810 changes: 405 additions & 405 deletions .gas-snapshot

Large diffs are not rendered by default.

8 changes: 0 additions & 8 deletions test/EnumerableSetLib.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -571,14 +571,6 @@ contract EnumerableSetLibTest is SoladyTest {
return bytes32Set.at(i);
}

function _brutalized(address a) private view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, gas())
result := or(shl(160, keccak256(0x00, 0x20)), a)
}
}

function _toUints(address[] memory a) private pure returns (uint256[] memory result) {
/// @solidity memory-safe-assembly
assembly {
Expand Down
9 changes: 1 addition & 8 deletions test/LibClone.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -486,17 +486,10 @@ contract LibCloneTest is SoladyTest, Clone {
this.checkStartsWith(bytes32((uint256(uint160(by)) << 96) | noise), notBy);
}

function checkStartsWith(bytes32 salt, address by) public view {
function checkStartsWith(bytes32 salt, address by) public pure {
LibClone.checkStartsWith(salt, _brutalized(by));
}

function _brutalized(address a) internal view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
result := or(a, shl(160, gas()))
}
}

function testCloneWithImmutableArgsRevertsIfDataTooBig() public {
uint256 n = 0xff9b;
bytes memory data = _dummyData(n);
Expand Down
10 changes: 0 additions & 10 deletions test/LibERC6551.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -180,14 +180,4 @@ contract LibERC6551Test is SoladyTest {
assertEq(t.tokenId, tokenId);
_checkMemory();
}

function _brutalized(address a) internal view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, gas())
result := or(shl(160, keccak256(0x00, 0x20)), a)
mstore(0x00, result)
mstore(0x20, result)
}
}
}
7 changes: 0 additions & 7 deletions test/SafeTransferLib.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -819,13 +819,6 @@ contract SafeTransferLibTest is SoladyTest {
}
}

function _brutalized(address a) internal pure returns (address result) {
/// @solidity memory-safe-assembly
assembly {
result := or(a, shl(160, keccak256(0x00, 0x20)))
}
}

struct _TestTemps {
address signer;
uint256 privateKey;
Expand Down
184 changes: 184 additions & 0 deletions test/utils/Brutalizer.sol
Original file line number Diff line number Diff line change
@@ -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));
}
}
Loading

0 comments on commit 58362f7

Please sign in to comment.