Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/healthy-teachers-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@eth-optimism/ci-builder': patch
'@eth-optimism/contracts-bedrock': patch
---

Add echidna tests
70 changes: 46 additions & 24 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ jobs:
FOUNDRY_PROFILE: ci
- run:
name: gas snapshot
no_output_timeout: 15m
command: |
forge --version
yarn gas-snapshot --check
Expand All @@ -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
Expand All @@ -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 <<parameters.echidna_target>>
command: yarn echidna:<<parameters.echidna_target>>
working_directory: packages/contracts-bedrock
no_output_timeout: 20m

op-bindings-build:
docker:
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,6 @@ op-exporter


__pycache__

# Ignore echidna artifacts
crytic-export
1 change: 1 addition & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion ops/docker/ci-builder/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
59 changes: 59 additions & 0 deletions packages/contracts-bedrock/contracts/echidna/FuzzBurn.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
59 changes: 59 additions & 0 deletions packages/contracts-bedrock/contracts/echidna/FuzzEncoding.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading