// SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; pragma experimental ABIEncoderV2; import {StdStorage, stdStorage} from "./StdStorage.sol"; import {Vm} from "./Vm.sol"; abstract contract StdCheatsSafe { Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); bool private gasMeteringOff; // Data structures to parse Transaction objects from the broadcast artifact // that conform to EIP1559. The Raw structs is what is parsed from the JSON // and then converted to the one that is used by the user for better UX. struct RawTx1559 { string[] arguments; address contractAddress; string contractName; // json value name = function string functionSig; bytes32 hash; // json value name = tx RawTx1559Detail txDetail; // json value name = type string opcode; } struct RawTx1559Detail { AccessList[] accessList; bytes data; address from; bytes gas; bytes nonce; address to; bytes txType; bytes value; } struct Tx1559 { string[] arguments; address contractAddress; string contractName; string functionSig; bytes32 hash; Tx1559Detail txDetail; string opcode; } struct Tx1559Detail { AccessList[] accessList; bytes data; address from; uint256 gas; uint256 nonce; address to; uint256 txType; uint256 value; } // Data structures to parse Transaction objects from the broadcast artifact // that DO NOT conform to EIP1559. The Raw structs is what is parsed from the JSON // and then converted to the one that is used by the user for better UX. struct TxLegacy { string[] arguments; address contractAddress; string contractName; string functionSig; string hash; string opcode; TxDetailLegacy transaction; } struct TxDetailLegacy { AccessList[] accessList; uint256 chainId; bytes data; address from; uint256 gas; uint256 gasPrice; bytes32 hash; uint256 nonce; bytes1 opcode; bytes32 r; bytes32 s; uint256 txType; address to; uint8 v; uint256 value; } struct AccessList { address accessAddress; bytes32[] storageKeys; } // Data structures to parse Receipt objects from the broadcast artifact. // The Raw structs is what is parsed from the JSON // and then converted to the one that is used by the user for better UX. struct RawReceipt { bytes32 blockHash; bytes blockNumber; address contractAddress; bytes cumulativeGasUsed; bytes effectiveGasPrice; address from; bytes gasUsed; RawReceiptLog[] logs; bytes logsBloom; bytes status; address to; bytes32 transactionHash; bytes transactionIndex; } struct Receipt { bytes32 blockHash; uint256 blockNumber; address contractAddress; uint256 cumulativeGasUsed; uint256 effectiveGasPrice; address from; uint256 gasUsed; ReceiptLog[] logs; bytes logsBloom; uint256 status; address to; bytes32 transactionHash; uint256 transactionIndex; } // Data structures to parse the entire broadcast artifact, assuming the // transactions conform to EIP1559. struct EIP1559ScriptArtifact { string[] libraries; string path; string[] pending; Receipt[] receipts; uint256 timestamp; Tx1559[] transactions; TxReturn[] txReturns; } struct RawEIP1559ScriptArtifact { string[] libraries; string path; string[] pending; RawReceipt[] receipts; TxReturn[] txReturns; uint256 timestamp; RawTx1559[] transactions; } struct RawReceiptLog { // json value = address address logAddress; bytes32 blockHash; bytes blockNumber; bytes data; bytes logIndex; bool removed; bytes32[] topics; bytes32 transactionHash; bytes transactionIndex; bytes transactionLogIndex; } struct ReceiptLog { // json value = address address logAddress; bytes32 blockHash; uint256 blockNumber; bytes data; uint256 logIndex; bytes32[] topics; uint256 transactionIndex; uint256 transactionLogIndex; bool removed; } struct TxReturn { string internalType; string value; } struct Account { address addr; uint256 key; } function assumeNoPrecompiles(address addr) internal virtual { // Assembly required since `block.chainid` was introduced in 0.8.0. uint256 chainId; assembly { chainId := chainid() } assumeNoPrecompiles(addr, chainId); } function assumeNoPrecompiles(address addr, uint256 chainId) internal pure virtual { // Note: For some chains like Optimism these are technically predeploys (i.e. bytecode placed at a specific // address), but the same rationale for excluding them applies so we include those too. // These should be present on all EVM-compatible chains. vm.assume(addr < address(0x1) || addr > address(0x9)); // forgefmt: disable-start if (chainId == 10 || chainId == 420) { // https://github.com/ethereum-optimism/optimism/blob/eaa371a0184b56b7ca6d9eb9cb0a2b78b2ccd864/op-bindings/predeploys/addresses.go#L6-L21 vm.assume(addr < address(0x4200000000000000000000000000000000000000) || addr > address(0x4200000000000000000000000000000000000800)); } else if (chainId == 42161 || chainId == 421613) { // https://developer.arbitrum.io/useful-addresses#arbitrum-precompiles-l2-same-on-all-arb-chains vm.assume(addr < address(0x0000000000000000000000000000000000000064) || addr > address(0x0000000000000000000000000000000000000068)); } else if (chainId == 43114 || chainId == 43113) { // https://github.com/ava-labs/subnet-evm/blob/47c03fd007ecaa6de2c52ea081596e0a88401f58/precompile/params.go#L18-L59 vm.assume(addr < address(0x0100000000000000000000000000000000000000) || addr > address(0x01000000000000000000000000000000000000ff)); vm.assume(addr < address(0x0200000000000000000000000000000000000000) || addr > address(0x02000000000000000000000000000000000000FF)); vm.assume(addr < address(0x0300000000000000000000000000000000000000) || addr > address(0x03000000000000000000000000000000000000Ff)); } // forgefmt: disable-end } function readEIP1559ScriptArtifact(string memory path) internal view virtual returns (EIP1559ScriptArtifact memory) { string memory data = vm.readFile(path); bytes memory parsedData = vm.parseJson(data); RawEIP1559ScriptArtifact memory rawArtifact = abi.decode(parsedData, (RawEIP1559ScriptArtifact)); EIP1559ScriptArtifact memory artifact; artifact.libraries = rawArtifact.libraries; artifact.path = rawArtifact.path; artifact.timestamp = rawArtifact.timestamp; artifact.pending = rawArtifact.pending; artifact.txReturns = rawArtifact.txReturns; artifact.receipts = rawToConvertedReceipts(rawArtifact.receipts); artifact.transactions = rawToConvertedEIPTx1559s(rawArtifact.transactions); return artifact; } function rawToConvertedEIPTx1559s(RawTx1559[] memory rawTxs) internal pure virtual returns (Tx1559[] memory) { Tx1559[] memory txs = new Tx1559[](rawTxs.length); for (uint256 i; i < rawTxs.length; i++) { txs[i] = rawToConvertedEIPTx1559(rawTxs[i]); } return txs; } function rawToConvertedEIPTx1559(RawTx1559 memory rawTx) internal pure virtual returns (Tx1559 memory) { Tx1559 memory transaction; transaction.arguments = rawTx.arguments; transaction.contractName = rawTx.contractName; transaction.functionSig = rawTx.functionSig; transaction.hash = rawTx.hash; transaction.txDetail = rawToConvertedEIP1559Detail(rawTx.txDetail); transaction.opcode = rawTx.opcode; return transaction; } function rawToConvertedEIP1559Detail(RawTx1559Detail memory rawDetail) internal pure virtual returns (Tx1559Detail memory) { Tx1559Detail memory txDetail; txDetail.data = rawDetail.data; txDetail.from = rawDetail.from; txDetail.to = rawDetail.to; txDetail.nonce = _bytesToUint(rawDetail.nonce); txDetail.txType = _bytesToUint(rawDetail.txType); txDetail.value = _bytesToUint(rawDetail.value); txDetail.gas = _bytesToUint(rawDetail.gas); txDetail.accessList = rawDetail.accessList; return txDetail; } function readTx1559s(string memory path) internal view virtual returns (Tx1559[] memory) { string memory deployData = vm.readFile(path); bytes memory parsedDeployData = vm.parseJson(deployData, ".transactions"); RawTx1559[] memory rawTxs = abi.decode(parsedDeployData, (RawTx1559[])); return rawToConvertedEIPTx1559s(rawTxs); } function readTx1559(string memory path, uint256 index) internal view virtual returns (Tx1559 memory) { string memory deployData = vm.readFile(path); string memory key = string(abi.encodePacked(".transactions[", vm.toString(index), "]")); bytes memory parsedDeployData = vm.parseJson(deployData, key); RawTx1559 memory rawTx = abi.decode(parsedDeployData, (RawTx1559)); return rawToConvertedEIPTx1559(rawTx); } // Analogous to readTransactions, but for receipts. function readReceipts(string memory path) internal view virtual returns (Receipt[] memory) { string memory deployData = vm.readFile(path); bytes memory parsedDeployData = vm.parseJson(deployData, ".receipts"); RawReceipt[] memory rawReceipts = abi.decode(parsedDeployData, (RawReceipt[])); return rawToConvertedReceipts(rawReceipts); } function readReceipt(string memory path, uint256 index) internal view virtual returns (Receipt memory) { string memory deployData = vm.readFile(path); string memory key = string(abi.encodePacked(".receipts[", vm.toString(index), "]")); bytes memory parsedDeployData = vm.parseJson(deployData, key); RawReceipt memory rawReceipt = abi.decode(parsedDeployData, (RawReceipt)); return rawToConvertedReceipt(rawReceipt); } function rawToConvertedReceipts(RawReceipt[] memory rawReceipts) internal pure virtual returns (Receipt[] memory) { Receipt[] memory receipts = new Receipt[](rawReceipts.length); for (uint256 i; i < rawReceipts.length; i++) { receipts[i] = rawToConvertedReceipt(rawReceipts[i]); } return receipts; } function rawToConvertedReceipt(RawReceipt memory rawReceipt) internal pure virtual returns (Receipt memory) { Receipt memory receipt; receipt.blockHash = rawReceipt.blockHash; receipt.to = rawReceipt.to; receipt.from = rawReceipt.from; receipt.contractAddress = rawReceipt.contractAddress; receipt.effectiveGasPrice = _bytesToUint(rawReceipt.effectiveGasPrice); receipt.cumulativeGasUsed = _bytesToUint(rawReceipt.cumulativeGasUsed); receipt.gasUsed = _bytesToUint(rawReceipt.gasUsed); receipt.status = _bytesToUint(rawReceipt.status); receipt.transactionIndex = _bytesToUint(rawReceipt.transactionIndex); receipt.blockNumber = _bytesToUint(rawReceipt.blockNumber); receipt.logs = rawToConvertedReceiptLogs(rawReceipt.logs); receipt.logsBloom = rawReceipt.logsBloom; receipt.transactionHash = rawReceipt.transactionHash; return receipt; } function rawToConvertedReceiptLogs(RawReceiptLog[] memory rawLogs) internal pure virtual returns (ReceiptLog[] memory) { ReceiptLog[] memory logs = new ReceiptLog[](rawLogs.length); for (uint256 i; i < rawLogs.length; i++) { logs[i].logAddress = rawLogs[i].logAddress; logs[i].blockHash = rawLogs[i].blockHash; logs[i].blockNumber = _bytesToUint(rawLogs[i].blockNumber); logs[i].data = rawLogs[i].data; logs[i].logIndex = _bytesToUint(rawLogs[i].logIndex); logs[i].topics = rawLogs[i].topics; logs[i].transactionIndex = _bytesToUint(rawLogs[i].transactionIndex); logs[i].transactionLogIndex = _bytesToUint(rawLogs[i].transactionLogIndex); logs[i].removed = rawLogs[i].removed; } return logs; } // Deploy a contract by fetching the contract bytecode from // the artifacts directory // e.g. `deployCode(code, abi.encode(arg1,arg2,arg3))` function deployCode(string memory what, bytes memory args) internal virtual returns (address addr) { bytes memory bytecode = abi.encodePacked(vm.getCode(what), args); /// @solidity memory-safe-assembly assembly { addr := create(0, add(bytecode, 0x20), mload(bytecode)) } require(addr != address(0), "StdCheats deployCode(string,bytes): Deployment failed."); } function deployCode(string memory what) internal virtual returns (address addr) { bytes memory bytecode = vm.getCode(what); /// @solidity memory-safe-assembly assembly { addr := create(0, add(bytecode, 0x20), mload(bytecode)) } require(addr != address(0), "StdCheats deployCode(string): Deployment failed."); } /// @dev deploy contract with value on construction function deployCode(string memory what, bytes memory args, uint256 val) internal virtual returns (address addr) { bytes memory bytecode = abi.encodePacked(vm.getCode(what), args); /// @solidity memory-safe-assembly assembly { addr := create(val, add(bytecode, 0x20), mload(bytecode)) } require(addr != address(0), "StdCheats deployCode(string,bytes,uint256): Deployment failed."); } function deployCode(string memory what, uint256 val) internal virtual returns (address addr) { bytes memory bytecode = vm.getCode(what); /// @solidity memory-safe-assembly assembly { addr := create(val, add(bytecode, 0x20), mload(bytecode)) } require(addr != address(0), "StdCheats deployCode(string,uint256): Deployment failed."); } // creates a labeled address and the corresponding private key function makeAddrAndKey(string memory name) internal virtual returns (address addr, uint256 privateKey) { privateKey = uint256(keccak256(abi.encodePacked(name))); addr = vm.addr(privateKey); vm.label(addr, name); } // creates a labeled address function makeAddr(string memory name) internal virtual returns (address addr) { (addr,) = makeAddrAndKey(name); } // creates a struct containing both a labeled address and the corresponding private key function makeAccount(string memory name) internal virtual returns (Account memory account) { (account.addr, account.key) = makeAddrAndKey(name); } function deriveRememberKey(string memory mnemonic, uint32 index) internal virtual returns (address who, uint256 privateKey) { privateKey = vm.deriveKey(mnemonic, index); who = vm.rememberKey(privateKey); } function _bytesToUint(bytes memory b) private pure returns (uint256) { require(b.length <= 32, "StdCheats _bytesToUint(bytes): Bytes length exceeds 32."); return abi.decode(abi.encodePacked(new bytes(32 - b.length), b), (uint256)); } function isFork() internal view virtual returns (bool status) { try vm.activeFork() { status = true; } catch (bytes memory) {} } modifier skipWhenForking() { if (!isFork()) { _; } } modifier skipWhenNotForking() { if (isFork()) { _; } } modifier noGasMetering() { vm.pauseGasMetering(); // To prevent turning gas monitoring back on with nested functions that use this modifier, // we check if gasMetering started in the off position. If it did, we don't want to turn // it back on until we exit the top level function that used the modifier // // i.e. funcA() noGasMetering { funcB() }, where funcB has noGasMetering as well. // funcA will have `gasStartedOff` as false, funcB will have it as true, // so we only turn metering back on at the end of the funcA bool gasStartedOff = gasMeteringOff; gasMeteringOff = true; _; // if gas metering was on when this modifier was called, turn it back on at the end if (!gasStartedOff) { gasMeteringOff = false; vm.resumeGasMetering(); } } // a cheat for fuzzing addresses that are payable only // see https://github.com/foundry-rs/foundry/issues/3631 function assumePayable(address addr) internal virtual { (bool success,) = payable(addr).call{value: 0}(""); vm.assume(success); } } // Wrappers around cheatcodes to avoid footguns abstract contract StdCheats is StdCheatsSafe { using stdStorage for StdStorage; StdStorage private stdstore; Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); // Skip forward or rewind time by the specified number of seconds function skip(uint256 time) internal virtual { vm.warp(block.timestamp + time); } function rewind(uint256 time) internal virtual { vm.warp(block.timestamp - time); } // Setup a prank from an address that has some ether function hoax(address msgSender) internal virtual { vm.deal(msgSender, 1 << 128); vm.prank(msgSender); } function hoax(address msgSender, uint256 give) internal virtual { vm.deal(msgSender, give); vm.prank(msgSender); } function hoax(address msgSender, address origin) internal virtual { vm.deal(msgSender, 1 << 128); vm.prank(msgSender, origin); } function hoax(address msgSender, address origin, uint256 give) internal virtual { vm.deal(msgSender, give); vm.prank(msgSender, origin); } // Start perpetual prank from an address that has some ether function startHoax(address msgSender) internal virtual { vm.deal(msgSender, 1 << 128); vm.startPrank(msgSender); } function startHoax(address msgSender, uint256 give) internal virtual { vm.deal(msgSender, give); vm.startPrank(msgSender); } // Start perpetual prank from an address that has some ether // tx.origin is set to the origin parameter function startHoax(address msgSender, address origin) internal virtual { vm.deal(msgSender, 1 << 128); vm.startPrank(msgSender, origin); } function startHoax(address msgSender, address origin, uint256 give) internal virtual { vm.deal(msgSender, give); vm.startPrank(msgSender, origin); } function changePrank(address msgSender) internal virtual { vm.stopPrank(); vm.startPrank(msgSender); } function changePrank(address msgSender, address txOrigin) internal virtual { vm.stopPrank(); vm.startPrank(msgSender, txOrigin); } // The same as Vm's `deal` // Use the alternative signature for ERC20 tokens function deal(address to, uint256 give) internal virtual { vm.deal(to, give); } // Set the balance of an account for any ERC20 token // Use the alternative signature to update `totalSupply` function deal(address token, address to, uint256 give) internal virtual { deal(token, to, give, false); } // Set the balance of an account for any ERC1155 token // Use the alternative signature to update `totalSupply` function dealERC1155(address token, address to, uint256 id, uint256 give) internal virtual { dealERC1155(token, to, id, give, false); } function deal(address token, address to, uint256 give, bool adjust) internal virtual { // get current balance (, bytes memory balData) = token.call(abi.encodeWithSelector(0x70a08231, to)); uint256 prevBal = abi.decode(balData, (uint256)); // update balance stdstore.target(token).sig(0x70a08231).with_key(to).checked_write(give); // update total supply if (adjust) { (, bytes memory totSupData) = token.call(abi.encodeWithSelector(0x18160ddd)); uint256 totSup = abi.decode(totSupData, (uint256)); if (give < prevBal) { totSup -= (prevBal - give); } else { totSup += (give - prevBal); } stdstore.target(token).sig(0x18160ddd).checked_write(totSup); } } function dealERC1155(address token, address to, uint256 id, uint256 give, bool adjust) internal virtual { // get current balance (, bytes memory balData) = token.call(abi.encodeWithSelector(0x00fdd58e, to, id)); uint256 prevBal = abi.decode(balData, (uint256)); // update balance stdstore.target(token).sig(0x00fdd58e).with_key(to).with_key(id).checked_write(give); // update total supply if (adjust) { (, bytes memory totSupData) = token.call(abi.encodeWithSelector(0xbd85b039, id)); require( totSupData.length != 0, "StdCheats deal(address,address,uint,uint,bool): target contract is not ERC1155Supply." ); uint256 totSup = abi.decode(totSupData, (uint256)); if (give < prevBal) { totSup -= (prevBal - give); } else { totSup += (give - prevBal); } stdstore.target(token).sig(0xbd85b039).with_key(id).checked_write(totSup); } } function dealERC721(address token, address to, uint256 id) internal virtual { // check if token id is already minted and the actual owner. (bool successMinted, bytes memory ownerData) = token.staticcall(abi.encodeWithSelector(0x6352211e, id)); require(successMinted, "StdCheats deal(address,address,uint,bool): id not minted."); // get owner current balance (, bytes memory fromBalData) = token.call(abi.encodeWithSelector(0x70a08231, abi.decode(ownerData, (address)))); uint256 fromPrevBal = abi.decode(fromBalData, (uint256)); // get new user current balance (, bytes memory toBalData) = token.call(abi.encodeWithSelector(0x70a08231, to)); uint256 toPrevBal = abi.decode(toBalData, (uint256)); // update balances stdstore.target(token).sig(0x70a08231).with_key(abi.decode(ownerData, (address))).checked_write(--fromPrevBal); stdstore.target(token).sig(0x70a08231).with_key(to).checked_write(++toPrevBal); // update owner stdstore.target(token).sig(0x6352211e).with_key(id).checked_write(to); } }