diff --git a/.changeset/healthy-teachers-stare.md b/.changeset/healthy-teachers-stare.md new file mode 100644 index 0000000000000..05572c8fada12 --- /dev/null +++ b/.changeset/healthy-teachers-stare.md @@ -0,0 +1,6 @@ +--- +'@eth-optimism/ci-builder': patch +'@eth-optimism/contracts-bedrock': patch +--- + +Add echidna tests diff --git a/.circleci/config.yml b/.circleci/config.yml index 90fffaef24cb4..8b52708572f47 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -247,6 +247,7 @@ jobs: FOUNDRY_PROFILE: ci - run: name: gas snapshot + no_output_timeout: 15m command: | forge --version yarn gas-snapshot --check @@ -262,7 +263,7 @@ jobs: command: yarn storage-snapshot && git diff --exit-code .storage-layout working_directory: packages/contracts-bedrock - contracts-bedrock-echidna: + bedrock-echidna-build: docker: - image: ethereumoptimism/ci-builder:latest resource_class: large @@ -281,30 +282,27 @@ jobs: name: Compile with metadata hash command: yarn build:with-metadata working_directory: packages/contracts-bedrock + - persist_to_workspace: + root: . + paths: + - "node_modules" + - packages/contracts-bedrock + + bedrock-echidna-run: + docker: + - image: ethereumoptimism/ci-builder:latest + parameters: + echidna_target: + description: Which echidna fuzz contract to run + type: string + steps: + - checkout + - attach_workspace: {at: "."} - run: - name: Echidna Fuzz Aliasing - command: yarn echidna:aliasing || exit 0 - working_directory: packages/contracts-bedrock - - run: - name: Echidna Fuzz Burn - command: yarn echidna:burn || exit 0 - working_directory: packages/contracts-bedrock - - run: - name: Echidna Fuzz Encoding - command: yarn echidna:encoding || exit 0 - working_directory: packages/contracts-bedrock - - run: - name: Echidna Fuzz Portal - command: yarn enchidna:portal || exit 0 - working_directory: packages/contracts-bedrock - - run: - name: Echidna Fuzz Hashing - command: yarn echidna:hashing || exit 0 - working_directory: packages/contracts-bedrock - - run: - name: Echidna Fuzz Resource Metering - command: yarn echidna:metering || exit 0 + name: Echidna Fuzz <> + command: yarn echidna:<> working_directory: packages/contracts-bedrock + no_output_timeout: 20m op-bindings-build: docker: @@ -819,9 +817,33 @@ workflows: - contracts-bedrock-tests: requires: - yarn-monorepo - - contracts-bedrock-echidna: + - bedrock-echidna-build: requires: - yarn-monorepo + - bedrock-echidna-run: + echidna_target: aliasing + requires: + - bedrock-echidna-build + - bedrock-echidna-run: + echidna_target: burn + requires: + - bedrock-echidna-build + - bedrock-echidna-run: + echidna_target: encoding + requires: + - bedrock-echidna-build + - bedrock-echidna-run: + echidna_target: portal + requires: + - bedrock-echidna-build + - bedrock-echidna-run: + echidna_target: hashing + requires: + - bedrock-echidna-build + - bedrock-echidna-run: + echidna_target: metering + requires: + - bedrock-echidna-build - op-bindings-build: requires: - yarn-monorepo diff --git a/.gitignore b/.gitignore index 8bfae506506ef..c2745217cdf2c 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,6 @@ op-exporter __pycache__ + +# Ignore echidna artifacts +crytic-export diff --git a/codecov.yml b/codecov.yml index b676e0f87e12e..b0fbf79ffd845 100644 --- a/codecov.yml +++ b/codecov.yml @@ -7,6 +7,7 @@ ignore: - "**/*.t.sol" - "op-bindings/bindings/*.go" - "packages/contracts-bedrock/contracts/vendor/WETH9.sol" + - "packages/contracts-bedrock/contracts/echidna" coverage: status: patch: diff --git a/ops/docker/ci-builder/Dockerfile b/ops/docker/ci-builder/Dockerfile index bdd7e03f1ca93..5ba1a752bcc78 100644 --- a/ops/docker/ci-builder/Dockerfile +++ b/ops/docker/ci-builder/Dockerfile @@ -26,7 +26,7 @@ RUN source $HOME/.profile && \ FROM ethereum/client-go:alltools-v1.10.25 as geth -FROM ghcr.io/crytic/echidna/echidna:testing-master as echidna-test +FROM ghcr.io/crytic/echidna/echidna:v2.0.4 as echidna-test FROM python:3.8.13-slim-bullseye diff --git a/packages/contracts-bedrock/contracts/echidna/FuzzAddressAliasing.sol b/packages/contracts-bedrock/contracts/echidna/FuzzAddressAliasing.sol new file mode 100644 index 0000000000000..98b7f8c07ef2a --- /dev/null +++ b/packages/contracts-bedrock/contracts/echidna/FuzzAddressAliasing.sol @@ -0,0 +1,33 @@ +pragma solidity 0.8.15; + +import { AddressAliasHelper } from "../vendor/AddressAliasHelper.sol"; + +contract EchidnaFuzzAddressAliasing { + bool failedRoundtrip; + + /** + * @notice Takes an address to be aliased with AddressAliasHelper and then unaliased + * and updates the test contract's state indicating if the round trip encoding + * failed. + */ + function testRoundTrip(address addr) public { + // Alias our address + address aliasedAddr = AddressAliasHelper.applyL1ToL2Alias(addr); + + // Unalias our address + address undoneAliasAddr = AddressAliasHelper.undoL1ToL2Alias(aliasedAddr); + + // If our round trip aliasing did not return the original result, set our state. + if (addr != undoneAliasAddr) { + failedRoundtrip = true; + } + } + + /** + * @notice Verifies that testRoundTrip(...) did not ever fail. + */ + function echidna_round_trip_aliasing() public view returns (bool) { + // ASSERTION: The round trip aliasing done in testRoundTrip(...) should never fail. + return !failedRoundtrip; + } +} diff --git a/packages/contracts-bedrock/contracts/echidna/FuzzBurn.sol b/packages/contracts-bedrock/contracts/echidna/FuzzBurn.sol new file mode 100644 index 0000000000000..451460ff84a7b --- /dev/null +++ b/packages/contracts-bedrock/contracts/echidna/FuzzBurn.sol @@ -0,0 +1,59 @@ +pragma solidity 0.8.15; + +import { Burn } from "../libraries/Burn.sol"; + +contract EchidnaFuzzBurn { + bool failedEthBurn; + bool failedGasBurn; + + /** + * @notice Takes an integer amount of eth to burn through the Burn library and + * updates the contract state if an incorrect amount of eth moved from the contract + */ + function testBurn(uint256 _value) public { + // cache the contract's eth balance + uint256 preBurnBalance = address(this).balance; + + // execute a burn of _value eth + // (may way to add guardrails to this value rather than a truly unbounded uint256) + Burn.eth(_value); + + // check that exactly _value eth was transfered from the contract + if (address(this).balance != preBurnBalance - _value) { + failedEthBurn = true; + } + } + + /** + * @notice Takes an integer amount of gas to burn through the Burn library and + * updates the contract state if at least that amount of gas was not burned + * by the library + */ + function testGas(uint256 _value) public view { + // cache the contract's current remaining gas + uint256 preBurnGas = gasleft(); + + // execute the gas burn + Burn.gas(_value); + + // cache the remaining gas post burn + uint256 postBurnGas = gasleft(); + + // check that at least _value gas was burnt + if (postBurnGas > preBurnGas - _value) { + failedGasBurn; + } + } + + function echidna_burn_eth() public view returns (bool) { + // ASSERTION: The amount burned should always match the amount passed exactly + return !failedEthBurn; + } + + function echidna_burn_gas() public view returns (bool) { + // ASSERTION: The amount of gas burned should be strictly greater than the + // the amount passed as _value (minimum _value + whatever minor overhead to + // the value after the call) + return !failedGasBurn; + } +} diff --git a/packages/contracts-bedrock/contracts/echidna/FuzzEncoding.sol b/packages/contracts-bedrock/contracts/echidna/FuzzEncoding.sol new file mode 100644 index 0000000000000..ec37079a54fba --- /dev/null +++ b/packages/contracts-bedrock/contracts/echidna/FuzzEncoding.sol @@ -0,0 +1,59 @@ +pragma solidity 0.8.15; + +import { Encoding } from "../libraries/Encoding.sol"; + +contract EchidnaFuzzEncoding { + bool failedRoundtripAToB; + bool failedRoundtripBToA; + + /** + * @notice Takes a pair of integers to be encoded into a versioned nonce with the + * Encoding library and then decoded and updates the test contract's state + * indicating if the round trip encoding failed. + */ + function testRoundTripAToB(uint240 _nonce, uint16 _version) public { + // encode the address + uint256 encodedVersionedNonce = Encoding.encodeVersionedNonce(_nonce, _version); + + // Unalias our address + uint240 decodedNonce; + uint16 decodedVersion; + + (decodedNonce, decodedVersion) = Encoding.decodeVersionedNonce(encodedVersionedNonce); + + // If our round trip encoding did not return the original result, set our state. + if ((decodedNonce != _nonce) || (decodedVersion != _version)) { + failedRoundtripAToB = true; + } + } + + /** + * @notice Takes an integer representing a packed version and nonce and attempts + * to decode them using the Encoding library before re-encoding and updates + * the test contract's state indicating if the round trip encoding failed. + */ + function testRoundTripBToA(uint256 _versionedNonce) public { + // Unalias our address + uint240 decodedNonce; + uint16 decodedVersion; + + (decodedNonce, decodedVersion) = Encoding.decodeVersionedNonce(_versionedNonce); + + // encode the address + uint256 encodedVersionedNonce = Encoding.encodeVersionedNonce(decodedNonce, decodedVersion); + + // If our round trip encoding did not return the original result, set our state. + if (encodedVersionedNonce != _versionedNonce) { + failedRoundtripBToA = true; + } + } + + /** + * @notice Verifies that testRoundTrip(...) did not ever fail. + */ + function echidna_round_trip_encoding() public view returns (bool) { + // ASSERTION: The round trip encoding done in testRoundTripAToB(...)/BToA(...) should never + // fail. + return !failedRoundtripAToB && !failedRoundtripBToA; + } +} diff --git a/packages/contracts-bedrock/contracts/echidna/FuzzHashing.sol b/packages/contracts-bedrock/contracts/echidna/FuzzHashing.sol new file mode 100644 index 0000000000000..8b7fb283c7fc1 --- /dev/null +++ b/packages/contracts-bedrock/contracts/echidna/FuzzHashing.sol @@ -0,0 +1,131 @@ +pragma solidity 0.8.15; + +import { Hashing } from "../libraries/Hashing.sol"; +import { Encoding } from "../libraries/Encoding.sol"; + +contract EchidnaFuzzHashing { + bool failedCrossDomainHashHighVersion; + bool failedCrossDomainHashV0; + bool failedCrossDomainHashV1; + + /** + * @notice Takes the necessary parameters to perform a cross domain hash with a randomly + * generated version. Only schema versions 0 and 1 are supported and all others should revert. + */ + function testHashCrossDomainMessageHighVersion( + uint16 _version, + uint240 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _gasLimit, + bytes memory _data + ) public { + // generate the versioned nonce + uint256 encodedNonce = Encoding.encodeVersionedNonce(_nonce, _version); + + // hash the cross domain message. we don't need to store the result since the function + // validates and should revert if an invalid version (>1) is encoded + Hashing.hashCrossDomainMessage(encodedNonce, _sender, _target, _value, _gasLimit, _data); + + // check that execution never makes it this far for an invalid version + if (_version > 1) { + failedCrossDomainHashHighVersion = true; + } + } + + /** + * @notice Takes the necessary parameters to perform a cross domain hash using the v0 schema + * and compares the output of a call to the unversioned function to the v0 function directly + */ + function testHashCrossDomainMessageV0( + uint240 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _gasLimit, + bytes memory _data + ) public { + // generate the versioned nonce with the version set to 0 + uint256 encodedNonce = Encoding.encodeVersionedNonce(_nonce, 0); + + // hash the cross domain message using the unversioned and versioned functions for + // comparison + bytes32 sampleHash1 = Hashing.hashCrossDomainMessage( + encodedNonce, + _sender, + _target, + _value, + _gasLimit, + _data + ); + bytes32 sampleHash2 = Hashing.hashCrossDomainMessageV0( + _target, + _sender, + _data, + encodedNonce + ); + + // check that the output of both functions matches + if (sampleHash1 != sampleHash2) { + failedCrossDomainHashV0 = true; + } + } + + /** + * @notice Takes the necessary parameters to perform a cross domain hash using the v1 schema + * and compares the output of a call to the unversioned function to the v1 function directly + */ + function testHashCrossDomainMessageV1( + uint240 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _gasLimit, + bytes memory _data + ) public { + // generate the versioned nonce with the version set to 1 + uint256 encodedNonce = Encoding.encodeVersionedNonce(_nonce, 1); + + // hash the cross domain message using the unversioned and versioned functions for + // comparison + bytes32 sampleHash1 = Hashing.hashCrossDomainMessage( + encodedNonce, + _sender, + _target, + _value, + _gasLimit, + _data + ); + bytes32 sampleHash2 = Hashing.hashCrossDomainMessageV1( + encodedNonce, + _sender, + _target, + _value, + _gasLimit, + _data + ); + + // check that the output of both functions matches + if (sampleHash1 != sampleHash2) { + failedCrossDomainHashV1 = true; + } + } + + function echidna_hash_xdomain_msg_high_version() public view returns (bool) { + // ASSERTION: A call to hashCrossDomainMessage will never succeed for a version > 1 + return !failedCrossDomainHashHighVersion; + } + + function echidna_hash_xdomain_msg_0() public view returns (bool) { + // ASSERTION: A call to hashCrossDomainMessage and hashCrossDomainMessageV0 + // should always match when the version passed is 0 + return !failedCrossDomainHashV0; + } + + function echidna_hash_xdomain_msg_1() public view returns (bool) { + // ASSERTION: A call to hashCrossDomainMessage and hashCrossDomainMessageV1 + // should always match when the version passed is 1 + return !failedCrossDomainHashV1; + } +} diff --git a/packages/contracts-bedrock/contracts/echidna/FuzzOptimismPortal.sol b/packages/contracts-bedrock/contracts/echidna/FuzzOptimismPortal.sol new file mode 100644 index 0000000000000..95e4a072d8a26 --- /dev/null +++ b/packages/contracts-bedrock/contracts/echidna/FuzzOptimismPortal.sol @@ -0,0 +1,128 @@ +pragma solidity 0.8.15; + +import { OptimismPortal } from "../L1/OptimismPortal.sol"; +import { L2OutputOracle } from "../L1/L2OutputOracle.sol"; +import { AddressAliasHelper } from "../vendor/AddressAliasHelper.sol"; + +contract EchidnaFuzzOptimismPortal is OptimismPortal { + uint256 reinitializedCount; + bool failedDepositCreationNonZeroAddr; + bool failedAliasingContractFromAddr; + bool failedNoAliasingFromEOA; + bool failedMintedLessThanTaken; + + constructor() OptimismPortal(L2OutputOracle(address(0)), 10) { + // Note: The base constructor will call initialize() once here. + } + + /** + * @notice This method calls upon OptimismPortal.initialize() to ensure + * no additional initializations are possible. + */ + function initializeEx() public { + super.initialize(); + reinitializedCount++; + } + + /** + * @notice This method calls upon OptimismPortal.depositTransaction() to ensure + * a deposit with _isCreation set to true never succeeds with a _to address that + * has a non-zero value. + */ + function depositTransactionIsCreation( + address _to, + uint256 _value, + uint64 _gasLimit, + bytes memory _data + ) public { + // Deposit with our given fuzz parameters and _isCreation set to true + depositTransaction(_to, _value, _gasLimit, true, _data); + + // If we did not revert and our _to address is not zero, flag a failure. + if (_to != address(0x0)) { + failedDepositCreationNonZeroAddr = true; + } + } + + /** + * @notice This method calls upon OptimismPortal.depositTransaction() from a + * contract address (itself, it performs an external call) to ensure contract + * aliasing is tested by depositTransactionTestInternal. + */ + function depositTransactionFromContract( + address _to, + uint256 _value, + uint64 _gasLimit, + bool _isCreation, + bytes memory _data + ) public payable { + // We perform an external call to our own address to trigger the conditions for a deposit + // by a contract address. This is because when we perform an external call, the receiving + // function will see msg.sender as the caller's address. + // Because we provide a function to ensure a call from a contract address, we'll be sure the + // fuzzer tested this case. + OptimismPortal(payable(this)).depositTransaction{ value: msg.value }( + _to, + _value, + _gasLimit, + _isCreation, + _data + ); + } + + /** + * @notice This override is called at the end of OptimismPortal.depositTransaction() + * so that we can sanity check all of the input and omitted data. + * + * Note: This is currently disabled (by setting the visibility to internal), as it required + * modifying the target contracts. We keep it here for posterity. + */ + function depositTransactionTestInternal( + address from, + address, + uint256, + uint256 mintValue, + uint256, + uint64, + bool, + bytes memory + ) internal { + // Check if the caller is a contract and confirm our address aliasing properties + if (msg.sender != tx.origin) { + // If the caller is a contract, we expect the address to be aliased. + if (AddressAliasHelper.undoL1ToL2Alias(from) != msg.sender) { + failedAliasingContractFromAddr = true; + } + } else { + // If the caller is an EOA address, we expect the address not to be aliased. + if (from != msg.sender) { + failedNoAliasingFromEOA = true; + } + } + + // If our mint value exceeds the amount paid, we failed a test. + if (mintValue > msg.value) { + failedMintedLessThanTaken = true; + } + } + + function echidna_never_initialize_twice() public view returns (bool) { + return reinitializedCount == 0; + } + + function echidna_never_nonzero_to_creation_deposit() public view returns (bool) { + return !failedDepositCreationNonZeroAddr; + } + + function echidna_alias_from_contract_deposit() public view returns (bool) { + return !failedAliasingContractFromAddr; + } + + function echidna_no_alias_from_EOA_deposit() public view returns (bool) { + return !failedNoAliasingFromEOA; + } + + function echidna_mint_less_than_taken() public view returns (bool) { + return !failedMintedLessThanTaken; + } +} diff --git a/packages/contracts-bedrock/contracts/echidna/FuzzResourceMetering.sol b/packages/contracts-bedrock/contracts/echidna/FuzzResourceMetering.sol new file mode 100644 index 0000000000000..8147b8dbf2490 --- /dev/null +++ b/packages/contracts-bedrock/contracts/echidna/FuzzResourceMetering.sol @@ -0,0 +1,114 @@ +pragma solidity 0.8.15; + +import { ResourceMetering } from "../L1/ResourceMetering.sol"; + +contract EchidnaFuzzResourceMetering is ResourceMetering { + bool failedMaxGasPerBlock; + bool failedRaiseBasefee; + bool failedLowerBasefee; + bool failedNeverBelowMinBasefee; + bool failedMaxRaiseBasefeePerBlock; + bool failedMaxLowerBasefeePerBlock; + + constructor() { + initialize(); + } + + function initialize() public initializer { + __ResourceMetering_init(); + } + + /** + * @notice Takes the necessary parameters to allow us to burn arbitrary amounts of gas to test + * the underlying resource metering/gas market logic + */ + function testBurn(uint256 _gasToBurn, bool _raiseBaseFee) public { + // Part 1: we cache the current param values and do some basic checks on them. + uint256 cachedPrevBaseFee = uint256(params.prevBaseFee); + uint256 cachedPrevBoughtGas = uint256(params.prevBoughtGas); + uint256 cachedPrevBlockNum = uint256(params.prevBlockNum); + uint256 maxBasefeeChange = cachedPrevBaseFee / uint256(BASE_FEE_MAX_CHANGE_DENOMINATOR); + + // check that the last block's base fee hasn't dropped below the minimum + if (cachedPrevBaseFee < uint256(MINIMUM_BASE_FEE)) { + failedNeverBelowMinBasefee = true; + } + // check that the last block didn't consume more than the max amount of gas + if (cachedPrevBoughtGas > uint256(MAX_RESOURCE_LIMIT)) { + failedMaxGasPerBlock = true; + } + + // Part2: we perform the gas burn + + // force the gasToBurn into the correct range based on whether we intend to + // raise or lower the basefee after this block, respectively + uint256 gasToBurn; + if (_raiseBaseFee) { + gasToBurn = + (_gasToBurn % (uint256(MAX_RESOURCE_LIMIT) - uint256(TARGET_RESOURCE_LIMIT))) + + uint256(TARGET_RESOURCE_LIMIT); + } else { + gasToBurn = _gasToBurn % uint256(TARGET_RESOURCE_LIMIT); + } + + _burnInternal(uint64(gasToBurn)); + + // Part 3: we run checks and modify our invariant flags based on the updated params values + + // if the last block used more than the target amount of gas (and there were no + // empty blocks in between), ensure this block's basefee increased, but not by + // more than the max amount per block + if ( + (cachedPrevBoughtGas > uint256(TARGET_RESOURCE_LIMIT)) && + (uint256(params.prevBlockNum) - cachedPrevBlockNum == 1) + ) { + failedRaiseBasefee = failedRaiseBasefee || (params.prevBaseFee <= cachedPrevBaseFee); + failedMaxRaiseBasefeePerBlock = + failedMaxRaiseBasefeePerBlock || + ((uint256(params.prevBaseFee) - cachedPrevBaseFee) < maxBasefeeChange); + } + // if the last blocked used less than the target amount of gas (and currently, + // that there were no empty blocks in between), ensure this block's basefee decreased, + // but not by more than the max amount + if ( + (cachedPrevBoughtGas < uint256(TARGET_RESOURCE_LIMIT)) || + (uint256(params.prevBlockNum) - cachedPrevBlockNum > 1) + ) { + failedLowerBasefee = + failedLowerBasefee || + (uint256(params.prevBaseFee) > cachedPrevBaseFee); + // TODO: account for empty blocks + if (params.prevBlockNum - cachedPrevBlockNum == 1) { + failedMaxLowerBasefeePerBlock = + failedMaxLowerBasefeePerBlock || + ((cachedPrevBaseFee - uint256(params.prevBaseFee)) < maxBasefeeChange); + } + } + } + + function _burnInternal(uint64 _gasToBurn) private metered(_gasToBurn) {} + + function echidna_high_usage_raise_basefee() public view returns (bool) { + return !failedRaiseBasefee; + } + + function echidna_low_usage_lower_basefee() public view returns (bool) { + return !failedLowerBasefee; + } + + function echidna_never_below_min_basefee() public view returns (bool) { + return !failedNeverBelowMinBasefee; + } + + function echidna_never_above_max_gas_limit() public view returns (bool) { + return !failedMaxGasPerBlock; + } + + function echidna_never_exceed_max_increase() public view returns (bool) { + return !failedMaxRaiseBasefeePerBlock; + } + + function echidna_never_exceed_max_decrease() public view returns (bool) { + return !failedMaxLowerBasefeePerBlock; + } +} diff --git a/packages/contracts-bedrock/package.json b/packages/contracts-bedrock/package.json index 46ea61795c22b..c41e51b2996f4 100644 --- a/packages/contracts-bedrock/package.json +++ b/packages/contracts-bedrock/package.json @@ -15,17 +15,17 @@ ], "scripts": { "build:forge": "forge build", - "build:with-metadata": "hardhat clean && FOUNDRY_PROFILE=echidna hardhat compile", + "build:with-metadata": "FOUNDRY_PROFILE=echidna yarn build:forge", "build:differential": "tsc scripts/differential-testing.ts --outDir dist --moduleResolution node --esModuleInterop", "prebuild": "yarn ts-node scripts/verify-foundry-install.ts", "build": "hardhat compile && yarn autogen:artifacts && yarn build:ts && yarn typechain", "build:ts": "tsc -p tsconfig.json", "autogen:artifacts": "ts-node scripts/generate-artifacts.ts", "deploy": "hardhat deploy", - "test": "yarn build:differential && forge test", - "coverage": "yarn build:differential && forge coverage", - "coverage:lcov": "yarn build:differential && forge coverage --report lcov", - "gas-snapshot": "forge snapshot --no-match-test 'differential|fuzz'", + "coverage": "yarn build:differential && forge coverage --no-match-contract 'EchidnaFuzz'", + "coverage:lcov": "yarn build:differential && forge coverage --no-match-contract 'EchidnaFuzz' --report lcov", + "test": "yarn build:differential && forge test --no-match-contract 'EchidnaFuzz'", + "gas-snapshot": "forge snapshot --no-match-test 'differential|fuzz' --no-match-contract 'EchidnaFuzz'", "storage-snapshot": "./scripts/storage-snapshot.sh", "validate-spacers": "hardhat validate-spacers", "slither": "./scripts/slither.sh", @@ -38,12 +38,12 @@ "lint:fix": "yarn lint:contracts:fix && yarn lint:ts:fix", "lint": "yarn lint:fix && yarn lint:check", "typechain": "typechain --target ethers-v5 --out-dir dist/types --glob 'artifacts/!(build-info)/**/+([a-zA-Z0-9_]).json'", - "echidna:aliasing": "echidna-test --contract FuzzAddressAliasing --format text --crytic-args --hardhat-ignore-compile .", - "echidna:burn": "echidna-test --contract FuzzBurn --format text --crytic-args --hardhat-ignore-compile .", - "echidna:encoding": "echidna-test --contract FuzzEncoding --format text --crytic-args --hardhat-ignore-compile .", - "echidna:portal": "echidna-test --contract FuzzOptimismPortal --format text --crytic-args --hardhat-ignore-compile .", - "echidna:hashing": "echidna-test --contract FuzzHashing --format text --crytic-args --hardhat-ignore-compile .", - "echidna:metering": "echidna-test --contract FuzzResourceMetering --format text --crytic-args --hardhat-ignore-compile ." + "echidna:aliasing": "echidna-test --contract EchidnaFuzzAddressAliasing --test-limit 5000 --format text --crytic-args --hardhat-ignore-compile .", + "echidna:burn": "echidna-test --contract EchidnaFuzzBurn --test-limit 5000 --format text --crytic-args --hardhat-ignore-compile .", + "echidna:encoding": "echidna-test --contract EchidnaFuzzEncoding --test-limit 5000 --format text --crytic-args --hardhat-ignore-compile .", + "echidna:portal": "echidna-test --contract EchidnaFuzzOptimismPortal --test-limit 5000 --format text --crytic-args --hardhat-ignore-compile .", + "echidna:hashing": "echidna-test --contract EchidnaFuzzHashing --test-limit 5000 --format text --crytic-args --hardhat-ignore-compile .", + "echidna:metering": "echidna-test --contract EchidnaFuzzResourceMetering --test-limit 5000 --format text --crytic-args --hardhat-ignore-compile ." }, "dependencies": { "@eth-optimism/core-utils": "^0.11.0",