Skip to content

Commit 55dce5f

Browse files
authored
evm: Echidna stateful fuzzing for NttManager (#393)
* 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
1 parent 0e861be commit 55dce5f

File tree

8 files changed

+961
-2
lines changed

8 files changed

+961
-2
lines changed

.github/workflows/evm.yml

+29
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,32 @@ jobs:
4141
run: |
4242
make check-format
4343
id: check
44+
45+
echidna:
46+
name: echidna
47+
runs-on: ubuntu-latest
48+
steps:
49+
- uses: actions/checkout@v4
50+
with:
51+
submodules: recursive
52+
53+
- name: Install Foundry
54+
uses: foundry-rs/foundry-toolchain@v1
55+
with:
56+
version: nightly
57+
58+
- name: Run Forge build
59+
run: |
60+
make test-push0
61+
id: build
62+
63+
- name: Install Echidna
64+
run: |
65+
curl -LO https://github.com/crytic/echidna/releases/download/v2.2.3/echidna-2.2.3-x86_64-linux.tar.gz
66+
tar -xzf echidna-2.2.3-x86_64-linux.tar.gz
67+
pip install crytic-compile
68+
69+
- name: Run Echidna
70+
run: |
71+
cd evm
72+
../echidna ./echidna --config echidna.yaml

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@
77
[submodule "lib/wormhole-solidity-sdk"]
88
path = evm/lib/wormhole-solidity-sdk
99
url = https://github.com/wormhole-foundation/wormhole-solidity-sdk
10+
[submodule "evm/lib/solidity-bytes-utils"]
11+
path = evm/lib/solidity-bytes-utils
12+
url = https://github.com/GNSPS/solidity-bytes-utils

Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,5 @@ test-evm:
3636

3737
# Verify that the contracts do not include PUSH0 opcodes
3838
test-push0:
39-
forge build --extra-output evm.bytecode.opcodes
40-
@if grep -qr --include \*.json PUSH0 ./out; then echo "Contract uses PUSH0 instruction" 1>&2; exit 1; else echo "PUSH0 Verification Succeeded"; fi
39+
cd evm && forge build --extra-output evm.bytecode.opcodes
40+
@if grep -qr --include \*.json PUSH0 ./evm/out; then echo "Contract uses PUSH0 instruction" 1>&2; exit 1; else echo "PUSH0 Verification Succeeded"; fi

evm/echidna.yaml

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
testMode: "assertion"
2+
3+
deployContracts: [
4+
["0x1f1", "TransceiverStructs"],
5+
]
6+
7+
cryticArgs: ["--compile-libraries=(TransceiverStructs,0x1f1)"]
8+
9+
# Default is 0x6000, but we need to increase this as not compiled via ir
10+
codeSize: 0x8000
11+
12+
stopOnFail: false
13+
14+
testLimit: 100000

evm/echidna/FuzzNttManager.sol

+835
Large diffs are not rendered by default.
+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
pragma solidity >=0.8.8 <0.9.0;
2+
3+
import "./IHevm.sol";
4+
5+
abstract contract FuzzingHelpers {
6+
address constant HEVM_ADDRESS = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D;
7+
IHevm hevm = IHevm(HEVM_ADDRESS);
8+
9+
10+
event LogAddress(address);
11+
event LogUint256(uint256);
12+
event LogString(string);
13+
event AssertFail(string);
14+
15+
// We need this to receive refunds for quotes
16+
receive() external payable {}
17+
18+
function clampBetween(uint256 value, uint256 low, uint256 high) internal returns (uint256){
19+
if (value < low || value > high) {
20+
uint ans = low + (value % (high - low + 1));
21+
return ans;
22+
}
23+
return value;
24+
}
25+
26+
function extractErrorSelector(
27+
bytes memory revertData
28+
) internal returns (uint256) {
29+
if (revertData.length < 4) {
30+
emit LogString("Return data too short.");
31+
return 0;
32+
}
33+
34+
uint256 errorSelector = uint256(
35+
(uint256(uint8(revertData[0])) << 24) |
36+
(uint256(uint8(revertData[1])) << 16) |
37+
(uint256(uint8(revertData[2])) << 8) |
38+
uint256(uint8(revertData[3]))
39+
);
40+
41+
return errorSelector;
42+
}
43+
44+
function extractErrorString(bytes memory revertData) internal returns (bytes32) {
45+
if (revertData.length < 68) revert();
46+
assembly {
47+
revertData := add(revertData, 0x04)
48+
}
49+
return keccak256(abi.encodePacked(abi.decode(revertData, (string))));
50+
}
51+
52+
function selectorToUint(bytes4 selector) internal returns (uint256) {
53+
return uint256(uint32(selector));
54+
}
55+
56+
function assertWithMsg(bool b, string memory reason) internal {
57+
if (!b) {
58+
emit AssertFail(reason);
59+
assert(false);
60+
}
61+
}
62+
63+
function minUint8(uint8 a, uint8 b) internal returns (uint8) {
64+
return a < b ? a : b;
65+
}
66+
67+
function minUint256(uint256 a, uint256 b) internal returns (uint256) {
68+
return a < b ? a : b;
69+
}
70+
}

evm/echidna/helpers/IHevm.sol

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pragma solidity >=0.8.8 <0.9.0;
2+
3+
interface IHevm {
4+
function prank(address) external;
5+
6+
function warp(uint256 newTimestamp) external;
7+
}

evm/lib/solidity-bytes-utils

Submodule solidity-bytes-utils added at e0115c4

0 commit comments

Comments
 (0)