Spaces:
Building
Building
| // SPDX-License-Identifier: MIT OR Apache-2.0 | |
| pragma solidity >=0.8.13 <0.9.0; | |
| import {StdStorage, stdStorage} from "./StdStorage.sol"; | |
| import {console2} from "./console2.sol"; | |
| import {Vm} from "./Vm.sol"; | |
| abstract contract StdCheatsSafe { | |
| Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); | |
| uint256 private constant UINT256_MAX = | |
| 115792089237316195423570985008687907853269984665640564039457584007913129639935; | |
| bool private gasMeteringOff; | |
| // Data structures to parse Transaction objects from the broadcast artifact | |
| // that conform to EIP1559. The Raw structs are what are 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 are what are 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; | |
| } | |
| enum AddressType { | |
| Payable, | |
| NonPayable, | |
| ZeroAddress, | |
| Precompile, | |
| ForgeAddress | |
| } | |
| // Checks that `addr` is not blacklisted by token contracts that have a blacklist. | |
| function assumeNotBlacklisted(address token, address addr) internal view virtual { | |
| // Nothing to check if `token` is not a contract. | |
| uint256 tokenCodeSize; | |
| assembly { | |
| tokenCodeSize := extcodesize(token) | |
| } | |
| require(tokenCodeSize > 0, "StdCheats assumeNotBlacklisted(address,address): Token address is not a contract."); | |
| bool success; | |
| bytes memory returnData; | |
| // 4-byte selector for `isBlacklisted(address)`, used by USDC. | |
| (success, returnData) = token.staticcall(abi.encodeWithSelector(0xfe575a87, addr)); | |
| vm.assume(!success || abi.decode(returnData, (bool)) == false); | |
| // 4-byte selector for `isBlackListed(address)`, used by USDT. | |
| (success, returnData) = token.staticcall(abi.encodeWithSelector(0xe47d6060, addr)); | |
| vm.assume(!success || abi.decode(returnData, (bool)) == false); | |
| } | |
| // Checks that `addr` is not blacklisted by token contracts that have a blacklist. | |
| // This is identical to `assumeNotBlacklisted(address,address)` but with a different name, for | |
| // backwards compatibility, since this name was used in the original PR which already has | |
| // a release. This function can be removed in a future release once we want a breaking change. | |
| function assumeNoBlacklisted(address token, address addr) internal view virtual { | |
| assumeNotBlacklisted(token, addr); | |
| } | |
| function assumeAddressIsNot(address addr, AddressType addressType) internal virtual { | |
| if (addressType == AddressType.Payable) { | |
| assumeNotPayable(addr); | |
| } else if (addressType == AddressType.NonPayable) { | |
| assumePayable(addr); | |
| } else if (addressType == AddressType.ZeroAddress) { | |
| assumeNotZeroAddress(addr); | |
| } else if (addressType == AddressType.Precompile) { | |
| assumeNotPrecompile(addr); | |
| } else if (addressType == AddressType.ForgeAddress) { | |
| assumeNotForgeAddress(addr); | |
| } | |
| } | |
| function assumeAddressIsNot(address addr, AddressType addressType1, AddressType addressType2) internal virtual { | |
| assumeAddressIsNot(addr, addressType1); | |
| assumeAddressIsNot(addr, addressType2); | |
| } | |
| function assumeAddressIsNot( | |
| address addr, | |
| AddressType addressType1, | |
| AddressType addressType2, | |
| AddressType addressType3 | |
| ) internal virtual { | |
| assumeAddressIsNot(addr, addressType1); | |
| assumeAddressIsNot(addr, addressType2); | |
| assumeAddressIsNot(addr, addressType3); | |
| } | |
| function assumeAddressIsNot( | |
| address addr, | |
| AddressType addressType1, | |
| AddressType addressType2, | |
| AddressType addressType3, | |
| AddressType addressType4 | |
| ) internal virtual { | |
| assumeAddressIsNot(addr, addressType1); | |
| assumeAddressIsNot(addr, addressType2); | |
| assumeAddressIsNot(addr, addressType3); | |
| assumeAddressIsNot(addr, addressType4); | |
| } | |
| // This function checks whether an address, `addr`, is payable. It works by sending 1 wei to | |
| // `addr` and checking the `success` return value. | |
| // NOTE: This function may result in state changes depending on the fallback/receive logic | |
| // implemented by `addr`, which should be taken into account when this function is used. | |
| function _isPayable(address addr) private returns (bool) { | |
| require( | |
| addr.balance < UINT256_MAX, | |
| "StdCheats _isPayable(address): Balance equals max uint256, so it cannot receive any more funds" | |
| ); | |
| uint256 origBalanceTest = address(this).balance; | |
| uint256 origBalanceAddr = address(addr).balance; | |
| vm.deal(address(this), 1); | |
| (bool success,) = payable(addr).call{value: 1}(""); | |
| // reset balances | |
| vm.deal(address(this), origBalanceTest); | |
| vm.deal(addr, origBalanceAddr); | |
| return success; | |
| } | |
| // NOTE: This function may result in state changes depending on the fallback/receive logic | |
| // implemented by `addr`, which should be taken into account when this function is used. See the | |
| // `_isPayable` method for more information. | |
| function assumePayable(address addr) internal virtual { | |
| vm.assume(_isPayable(addr)); | |
| } | |
| function assumeNotPayable(address addr) internal virtual { | |
| vm.assume(!_isPayable(addr)); | |
| } | |
| function assumeNotZeroAddress(address addr) internal pure virtual { | |
| vm.assume(addr != address(0)); | |
| } | |
| function assumeNotPrecompile(address addr) internal pure virtual { | |
| assumeNotPrecompile(addr, _pureChainId()); | |
| } | |
| function assumeNotPrecompile(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 are reserved by Ethereum and may be on all EVM-compatible chains. | |
| vm.assume(addr < address(0x1) || addr > address(0xff)); | |
| // forgefmt: disable-start | |
| if (chainId == 10 || chainId == 420 || chainId == 11155420) { | |
| // 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 assumeNotForgeAddress(address addr) internal pure virtual { | |
| // vm, console, and Create2Deployer addresses | |
| vm.assume( | |
| addr != address(vm) && addr != 0x000000000000000000636F6e736F6c652e6c6f67 | |
| && addr != 0x4e59b44847b379578588920cA78FbF26c0B4956C | |
| ); | |
| } | |
| function assumeUnusedAddress(address addr) internal view virtual { | |
| uint256 size; | |
| assembly { | |
| size := extcodesize(addr) | |
| } | |
| vm.assume(size == 0); | |
| assumeNotPrecompile(addr); | |
| assumeNotZeroAddress(addr); | |
| assumeNotForgeAddress(addr); | |
| } | |
| 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.contractAddress = rawTx.contractAddress; | |
| 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); | |
| assembly ("memory-safe") { | |
| 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); | |
| assembly ("memory-safe") { | |
| 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); | |
| assembly ("memory-safe") { | |
| 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); | |
| assembly ("memory-safe") { | |
| 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); | |
| } | |
| // Destroys an account immediately, sending the balance to beneficiary. | |
| // Destroying means: balance will be zero, code will be empty, and nonce will be 0 | |
| // This is similar to selfdestruct but not identical: selfdestruct destroys code and nonce | |
| // only after tx ends, this will run immediately. | |
| function destroyAccount(address who, address beneficiary) internal virtual { | |
| uint256 currBalance = who.balance; | |
| vm.etch(who, abi.encode()); | |
| vm.deal(who, 0); | |
| vm.resetNonce(who); | |
| uint256 beneficiaryBalance = beneficiary.balance; | |
| vm.deal(beneficiary, currBalance + beneficiaryBalance); | |
| } | |
| // 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(); | |
| } | |
| } | |
| // We use this complex approach of `_viewChainId` and `_pureChainId` to ensure there are no | |
| // compiler warnings when accessing chain ID in any solidity version supported by forge-std. We | |
| // can't simply access the chain ID in a normal view or pure function because the solc View Pure | |
| // Checker changed `chainid` from pure to view in 0.8.0. | |
| function _viewChainId() private view returns (uint256 chainId) { | |
| // Assembly required since `block.chainid` was introduced in 0.8.0. | |
| assembly { | |
| chainId := chainid() | |
| } | |
| address(this); // Silence warnings in older Solc versions. | |
| } | |
| function _pureChainId() private pure returns (uint256 chainId) { | |
| function() internal view returns (uint256) fnIn = _viewChainId; | |
| function() internal pure returns (uint256) pureChainId; | |
| assembly { | |
| pureChainId := fnIn | |
| } | |
| chainId = pureChainId(); | |
| } | |
| } | |
| // 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"))))); | |
| address private constant CONSOLE2_ADDRESS = 0x000000000000000000636F6e736F6c652e6c6f67; | |
| // Skip forward or rewind time by the specified number of seconds | |
| function skip(uint256 time) internal virtual { | |
| vm.warp(vm.getBlockTimestamp() + time); | |
| } | |
| function rewind(uint256 time) internal virtual { | |
| vm.warp(vm.getBlockTimestamp() - 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 { | |
| console2_log_StdCheats("changePrank is deprecated. Please use vm.startPrank instead."); | |
| vm.stopPrank(); | |
| vm.startPrank(msgSender); | |
| } | |
| function changePrank(address msgSender, address txOrigin) internal virtual { | |
| console2_log_StdCheats("changePrank is deprecated. Please use vm.startPrank instead."); | |
| 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.staticcall(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.staticcall(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.staticcall(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.staticcall(abi.encodeWithSelector(0xbd85b039, id)); | |
| require( | |
| totSupData.length != 0, | |
| "StdCheats dealERC1155(address,address,uint256,uint256,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 dealERC721(address,address,uint256): id not minted."); | |
| // get owner current balance | |
| (, bytes memory fromBalData) = | |
| token.staticcall(abi.encodeWithSelector(0x70a08231, abi.decode(ownerData, (address)))); | |
| uint256 fromPrevBal = abi.decode(fromBalData, (uint256)); | |
| // get new user current balance | |
| (, bytes memory toBalData) = token.staticcall(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); | |
| } | |
| function deployCodeTo(string memory what, address where) internal virtual { | |
| deployCodeTo(what, "", 0, where); | |
| } | |
| function deployCodeTo(string memory what, bytes memory args, address where) internal virtual { | |
| deployCodeTo(what, args, 0, where); | |
| } | |
| function deployCodeTo(string memory what, bytes memory args, uint256 value, address where) internal virtual { | |
| bytes memory creationCode = vm.getCode(what); | |
| vm.etch(where, abi.encodePacked(creationCode, args)); | |
| (bool success, bytes memory runtimeBytecode) = where.call{value: value}(""); | |
| require(success, "StdCheats deployCodeTo(string,bytes,uint256,address): Failed to create runtime bytecode."); | |
| vm.etch(where, runtimeBytecode); | |
| } | |
| // Used to prevent the compilation of console, which shortens the compilation time when console is not used elsewhere. | |
| function console2_log_StdCheats(string memory p0) private view { | |
| (bool status,) = address(CONSOLE2_ADDRESS).staticcall(abi.encodeWithSignature("log(string)", p0)); | |
| status; | |
| } | |
| } | |