-
Notifications
You must be signed in to change notification settings - Fork 160
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
21b8e75
commit 44d13b4
Showing
5 changed files
with
243 additions
and
212 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |
Oops, something went wrong.