Skip to content

Commit

Permalink
evm: Echidna stateful fuzzing for NttManager (#393)
Browse files Browse the repository at this point in the history
* Initial fuzzing harness

* forge install: solidity-bytes-utils

v0.8.2

* More fuzz test coverage

* More fuzz tests

* Fix ordering of checks

* Fuzz transfer qol entrypoint

* Refactor transfer fuzzing with fewer assumptions

* More fuzzing progress for arbitrary instructions

* More fuzzing

* Fuzz cancelling

* Get fuzz suite working again

* Fix recent changes and add to CI

* Fix action
  • Loading branch information
djb15 authored May 2, 2024
1 parent 0e861be commit 55dce5f
Show file tree
Hide file tree
Showing 8 changed files with 961 additions and 2 deletions.
29 changes: 29 additions & 0 deletions .github/workflows/evm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,32 @@ jobs:
run: |
make check-format
id: check

echidna:
name: echidna
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Run Forge build
run: |
make test-push0
id: build

- name: Install Echidna
run: |
curl -LO https://github.com/crytic/echidna/releases/download/v2.2.3/echidna-2.2.3-x86_64-linux.tar.gz
tar -xzf echidna-2.2.3-x86_64-linux.tar.gz
pip install crytic-compile
- name: Run Echidna
run: |
cd evm
../echidna ./echidna --config echidna.yaml
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "lib/wormhole-solidity-sdk"]
path = evm/lib/wormhole-solidity-sdk
url = https://github.com/wormhole-foundation/wormhole-solidity-sdk
[submodule "evm/lib/solidity-bytes-utils"]
path = evm/lib/solidity-bytes-utils
url = https://github.com/GNSPS/solidity-bytes-utils
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@ test-evm:

# Verify that the contracts do not include PUSH0 opcodes
test-push0:
forge build --extra-output evm.bytecode.opcodes
@if grep -qr --include \*.json PUSH0 ./out; then echo "Contract uses PUSH0 instruction" 1>&2; exit 1; else echo "PUSH0 Verification Succeeded"; fi
cd evm && forge build --extra-output evm.bytecode.opcodes
@if grep -qr --include \*.json PUSH0 ./evm/out; then echo "Contract uses PUSH0 instruction" 1>&2; exit 1; else echo "PUSH0 Verification Succeeded"; fi
14 changes: 14 additions & 0 deletions evm/echidna.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
testMode: "assertion"

deployContracts: [
["0x1f1", "TransceiverStructs"],
]

cryticArgs: ["--compile-libraries=(TransceiverStructs,0x1f1)"]

# Default is 0x6000, but we need to increase this as not compiled via ir
codeSize: 0x8000

stopOnFail: false

testLimit: 100000
835 changes: 835 additions & 0 deletions evm/echidna/FuzzNttManager.sol

Large diffs are not rendered by default.

70 changes: 70 additions & 0 deletions evm/echidna/helpers/FuzzingHelpers.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
pragma solidity >=0.8.8 <0.9.0;

import "./IHevm.sol";

abstract contract FuzzingHelpers {
address constant HEVM_ADDRESS = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D;
IHevm hevm = IHevm(HEVM_ADDRESS);


event LogAddress(address);
event LogUint256(uint256);
event LogString(string);
event AssertFail(string);

// We need this to receive refunds for quotes
receive() external payable {}

function clampBetween(uint256 value, uint256 low, uint256 high) internal returns (uint256){
if (value < low || value > high) {
uint ans = low + (value % (high - low + 1));
return ans;
}
return value;
}

function extractErrorSelector(
bytes memory revertData
) internal returns (uint256) {
if (revertData.length < 4) {
emit LogString("Return data too short.");
return 0;
}

uint256 errorSelector = uint256(
(uint256(uint8(revertData[0])) << 24) |
(uint256(uint8(revertData[1])) << 16) |
(uint256(uint8(revertData[2])) << 8) |
uint256(uint8(revertData[3]))
);

return errorSelector;
}

function extractErrorString(bytes memory revertData) internal returns (bytes32) {
if (revertData.length < 68) revert();
assembly {
revertData := add(revertData, 0x04)
}
return keccak256(abi.encodePacked(abi.decode(revertData, (string))));
}

function selectorToUint(bytes4 selector) internal returns (uint256) {
return uint256(uint32(selector));
}

function assertWithMsg(bool b, string memory reason) internal {
if (!b) {
emit AssertFail(reason);
assert(false);
}
}

function minUint8(uint8 a, uint8 b) internal returns (uint8) {
return a < b ? a : b;
}

function minUint256(uint256 a, uint256 b) internal returns (uint256) {
return a < b ? a : b;
}
}
7 changes: 7 additions & 0 deletions evm/echidna/helpers/IHevm.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pragma solidity >=0.8.8 <0.9.0;

interface IHevm {
function prank(address) external;

function warp(uint256 newTimestamp) external;
}
1 change: 1 addition & 0 deletions evm/lib/solidity-bytes-utils
Submodule solidity-bytes-utils added at e0115c

0 comments on commit 55dce5f

Please sign in to comment.