Skip to content

Commit

Permalink
feat: test_harness.sol
Browse files Browse the repository at this point in the history
  • Loading branch information
sambacha authored Nov 27, 2024
1 parent 92c6e6a commit dca7e6c
Showing 1 changed file with 341 additions and 0 deletions.
341 changes: 341 additions & 0 deletions test/test_harness.sol
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))
}
}
}

0 comments on commit dca7e6c

Please sign in to comment.