-
Notifications
You must be signed in to change notification settings - Fork 2
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
Showing
1 changed file
with
341 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,341 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.4; | ||
|
||
contract TestHarness { | ||
/// @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() private 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 memory with junk, for more robust testing of inline assembly | ||
/// which reads/write to the memory. | ||
modifier brutalizeMemory() { | ||
_brutalizeMemory(); | ||
_; | ||
_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) { | ||
/// @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) | ||
|
||
mstore(0x20, sValue) | ||
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()) | ||
} | ||
sstore(sSlot, 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) | ||
break | ||
} | ||
// 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 { | ||
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)) | ||
} | ||
// With a 1/2 chance, negate `r`. | ||
if iszero(and(0x20, d)) { r := not(r) } | ||
break | ||
} | ||
// Otherwise, just set `r` to `xor(sValue, r)`. | ||
r := xor(sValue, r) | ||
break | ||
} | ||
} | ||
} | ||
|
||
/// @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); | ||
/// @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) | ||
} | ||
} | ||
|
||
/// @dev Returns a random address. | ||
function _randomAddress() internal returns (address result) { | ||
result = address(uint160(_random())); | ||
} | ||
|
||
/// @dev Returns a random non-zero address. | ||
function _randomNonZeroAddress() internal returns (address result) { | ||
do { | ||
result = address(uint160(_random())); | ||
} 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. | ||
function _hem(uint256 x, uint256 min, uint256 max) internal pure virtual returns (uint256 result) { | ||
require(min <= max, "Max is less than min."); | ||
|
||
/// @solidity memory-safe-assembly | ||
assembly { | ||
// prettier-ignore | ||
for {} 1 {} { | ||
// If `x` is between `min` and `max`, return `x` directly. | ||
// This is to ensure that dictionary values | ||
// do not get shifted if the min is nonzero. | ||
// More info: https://github.com/foundry-rs/forge-std/issues/188 | ||
if iszero(or(lt(x, min), gt(x, max))) { | ||
result := x | ||
break | ||
} | ||
|
||
let size := add(sub(max, min), 1) | ||
if and(iszero(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)) | ||
break | ||
} | ||
|
||
// Otherwise, wrap x into the range [min, max], | ||
// i.e. the range is inclusive. | ||
if iszero(lt(x, max)) { | ||
let d := sub(x, max) | ||
let r := mod(d, size) | ||
if iszero(r) { | ||
result := max | ||
break | ||
} | ||
result := add(add(min, r), w) | ||
break | ||
} | ||
let d := sub(min, x) | ||
let r := mod(d, size) | ||
if iszero(r) { | ||
result := min | ||
break | ||
} | ||
result := add(sub(max, r), 1) | ||
break | ||
} | ||
} | ||
} | ||
|
||
/// @dev Deploys a contract via 0age's immutable create 2 factory for testing. | ||
function _safeCreate2(uint256 payableAmount, bytes32 salt, bytes memory initializationCode) | ||
internal | ||
returns (address deploymentAddress) | ||
{ | ||
// Canonical address of 0age's immutable create 2 factory. | ||
address c2f = 0x0000000000FFe8B47B3e2130213B802212439497; | ||
uint256 c2fCodeLength; | ||
/// @solidity memory-safe-assembly | ||
assembly { | ||
c2fCodeLength := extcodesize(c2f) | ||
} | ||
if (c2fCodeLength == 0) { | ||
bytes memory ic2fBytecode = | ||
hex"60806040526004361061003f5760003560e01c806308508b8f1461004457806364e030871461009857806385cf97ab14610138578063a49a7c90146101bc575b600080fd5b34801561005057600080fd5b506100846004803603602081101561006757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166101ec565b604080519115158252519081900360200190f35b61010f600480360360408110156100ae57600080fd5b813591908101906040810160208201356401000000008111156100d057600080fd5b8201836020820111156100e257600080fd5b8035906020019184600183028401116401000000008311171561010457600080fd5b509092509050610217565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561014457600080fd5b5061010f6004803603604081101561015b57600080fd5b8135919081019060408101602082013564010000000081111561017d57600080fd5b82018360208201111561018f57600080fd5b803590602001918460018302840111640100000000831117156101b157600080fd5b509092509050610592565b3480156101c857600080fd5b5061010f600480360360408110156101df57600080fd5b508035906020013561069e565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205460ff1690565b600083606081901c33148061024c57507fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008116155b6102a1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260458152602001806107746045913960600191505060405180910390fd5b606084848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920182905250604051855195965090943094508b93508692506020918201918291908401908083835b6020831061033557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016102f8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff018019909216911617905260408051929094018281037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00183528085528251928201929092207fff000000000000000000000000000000000000000000000000000000000000008383015260609890981b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016602183015260358201969096526055808201979097528251808203909701875260750182525084519484019490942073ffffffffffffffffffffffffffffffffffffffff81166000908152938490529390922054929350505060ff16156104a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603f815260200180610735603f913960400191505060405180910390fd5b81602001825188818334f5955050508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161461053a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260468152602001806107b96046913960600191505060405180910390fd5b50505073ffffffffffffffffffffffffffffffffffffffff8116600090815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790559392505050565b6000308484846040516020018083838082843760408051919093018181037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001825280845281516020928301207fff000000000000000000000000000000000000000000000000000000000000008383015260609990991b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166021820152603581019790975260558088019890985282518088039098018852607590960182525085519585019590952073ffffffffffffffffffffffffffffffffffffffff81166000908152948590529490932054939450505060ff909116159050610697575060005b9392505050565b604080517fff000000000000000000000000000000000000000000000000000000000000006020808301919091523060601b6021830152603582018590526055808301859052835180840390910181526075909201835281519181019190912073ffffffffffffffffffffffffffffffffffffffff81166000908152918290529190205460ff161561072e575060005b9291505056fe496e76616c696420636f6e7472616374206372656174696f6e202d20636f6e74726163742068617320616c7265616479206265656e206465706c6f7965642e496e76616c69642073616c74202d206669727374203230206279746573206f66207468652073616c74206d757374206d617463682063616c6c696e6720616464726573732e4661696c656420746f206465706c6f7920636f6e7472616374207573696e672070726f76696465642073616c7420616e6420696e697469616c697a6174696f6e20636f64652ea265627a7a723058202bdc55310d97c4088f18acf04253db593f0914059f0c781a9df3624dcef0d1cf64736f6c634300050a0032"; | ||
/// @solidity memory-safe-assembly | ||
assembly { | ||
let m := mload(0x40) | ||
mstore(m, 0xb4d6c782) // `etch(address,bytes)`. | ||
mstore(add(m, 0x20), c2f) | ||
mstore(add(m, 0x40), 0x40) | ||
let n := mload(ic2fBytecode) | ||
mstore(add(m, 0x60), n) | ||
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) } | ||
} | ||
} | ||
/// @solidity memory-safe-assembly | ||
assembly { | ||
let m := mload(0x40) | ||
let n := mload(initializationCode) | ||
mstore(m, 0x64e03087) // `safeCreate2(bytes32,bytes)`. | ||
mstore(add(m, 0x20), salt) | ||
mstore(add(m, 0x40), 0x40) | ||
mstore(add(m, 0x60), n) | ||
// prettier-ignore | ||
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { | ||
mstore(add(add(m, 0x80), i), mload(add(add(initializationCode, 0x20), i))) | ||
} | ||
if iszero(call(gas(), c2f, payableAmount, add(m, 0x1c), add(n, 0x64), m, 0x20)) { | ||
returndatacopy(m, m, returndatasize()) | ||
revert(m, returndatasize()) | ||
} | ||
deploymentAddress := mload(m) | ||
} | ||
} | ||
|
||
/// @dev Deploys a contract via 0age's immutable create 2 factory for testing. | ||
function _safeCreate2(bytes32 salt, bytes memory initializationCode) internal returns (address deploymentAddress) { | ||
deploymentAddress = _safeCreate2(0, salt, initializationCode); | ||
} | ||
|
||
/// @dev This function will make forge's gas output display the approximate codesize of | ||
/// the test contract as the amount of gas burnt. Useful for quick guess checking if | ||
/// certain optimizations actually compiles to similar bytecode. | ||
function test__codesize() external view { | ||
/// @solidity memory-safe-assembly | ||
assembly { | ||
// If the caller is the contract itself (i.e. recursive call), burn all the gas. | ||
if eq(caller(), address()) { invalid() } | ||
mstore(0x00, 0xf09ff470) // Store the function selector of `test__codesize()`. | ||
pop(staticcall(codesize(), address(), 0x1c, 0x04, 0x00, 0x00)) | ||
} | ||
} | ||
} |