diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml index bfc779835..643216b7a 100644 --- a/.github/workflows/foundry.yml +++ b/.github/workflows/foundry.yml @@ -18,11 +18,13 @@ jobs: # Forge Test # ----------------------------------------------------------------------- - test: + test-suite: name: Test runs-on: ubuntu-latest strategy: - fail-fast: true + matrix: + suite: [Unit, Integration, Fork] + steps: # Check out repository with all submodules for complete codebase access. - uses: actions/checkout@v4 @@ -30,7 +32,7 @@ jobs: submodules: recursive # Install the Foundry toolchain. - - name: "Install Foundry" + - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: version: stable @@ -41,32 +43,22 @@ jobs: forge fmt --check id: fmt - # Install Bun package manager for JavaScript dependencies (faster than npm). - - name: "Install Bun" - uses: "oven-sh/setup-bun@v1" - - # Install NPM packages. - - name: "Install NPM Packages" - run: bun install - - # Run Solhint linter to check for Solidity code quality issues. - - name: "Solhint" - run: bun run hint - # Build the project and display contract sizes. - - name: "Forge Build" + - name: Forge Build run: | forge --version forge build --sizes - id: build - # Run local tests (unit and integration). - - name: "Forge Test (Local)" - run: forge test -vvv - - # Run integration tests using a mainnet fork. - - name: "Forge Test Integration (Fork)" - run: FOUNDRY_PROFILE=forktest forge test --match-contract Integration -vvv + # Run the test suite in parallel based on the matrix configuration. + - name: Run ${{ matrix.suite }} tests + run: | + case "${{ matrix.suite }}" in + Unit) forge test --no-match-contract Integration ;; + Integration) forge test --match-contract Integration ;; + Fork) forge test --match-contract Integration ;; + esac + env: + FOUNDRY_PROFILE: ${{ matrix.suite == 'Fork' && 'forktest' || 'ci' }} # ----------------------------------------------------------------------- # Forge Test (Intense) diff --git a/src/test/unit/libraries/BytesLibUnit.t.sol b/src/test/unit/libraries/BytesLibUnit.t.sol index 50583484e..beca33dc1 100644 --- a/src/test/unit/libraries/BytesLibUnit.t.sol +++ b/src/test/unit/libraries/BytesLibUnit.t.sol @@ -4,33 +4,83 @@ pragma solidity ^0.8.27; import "forge-std/Test.sol"; import "src/contracts/libraries/BytesLib.sol"; +contract BytesLibHarness { + function concat(bytes memory a, bytes memory b) public pure returns (bytes memory) { + return BytesLib.concat(a, b); + } + + function slice(bytes memory data, uint256 start, uint256 length) public pure returns (bytes memory) { + return BytesLib.slice(data, start, length); + } + + function toAddress(bytes memory data, uint256 offset) public pure returns (address) { + return BytesLib.toAddress(data, offset); + } + + function toUint256(bytes memory data, uint256 offset) public pure returns (uint256) { + return BytesLib.toUint256(data, offset); + } + + function toUint128(bytes memory data, uint256 offset) public pure returns (uint128) { + return BytesLib.toUint128(data, offset); + } + + function toUint96(bytes memory data, uint256 offset) public pure returns (uint96) { + return BytesLib.toUint96(data, offset); + } + + function toUint64(bytes memory data, uint256 offset) public pure returns (uint64) { + return BytesLib.toUint64(data, offset); + } + + function toUint32(bytes memory data, uint256 offset) public pure returns (uint32) { + return BytesLib.toUint32(data, offset); + } + + function toUint16(bytes memory data, uint256 offset) public pure returns (uint16) { + return BytesLib.toUint16(data, offset); + } + + function toUint8(bytes memory data, uint256 offset) public pure returns (uint8) { + return BytesLib.toUint8(data, offset); + } + + function equal(bytes memory a, bytes memory b) public pure returns (bool) { + return BytesLib.equal(a, b); + } +} + contract BytesLibUnitTests is Test { - using BytesLib for bytes; + BytesLibHarness harness; + + function setUp() public { + harness = new BytesLibHarness(); + } - function test_Concat_Basic() public pure { + function test_Concat_Basic() public view { bytes memory a = hex"1234"; bytes memory b = hex"5678"; bytes memory expected = hex"12345678"; - bytes memory result = a.concat(b); - assertTrue(result.equal(expected)); + bytes memory result = harness.concat(a, b); + assertTrue(harness.equal(result, expected)); } - function test_Concat_EmptyInputs() public pure { + function test_Concat_EmptyInputs() public view { bytes memory empty = hex""; bytes memory data = hex"1234"; - assertTrue(empty.concat(empty).equal(empty)); - assertTrue(data.concat(empty).equal(data)); - assertTrue(empty.concat(data).equal(data)); + assertTrue(harness.equal(harness.concat(empty, empty), empty)); + assertTrue(harness.equal(harness.concat(data, empty), data)); + assertTrue(harness.equal(harness.concat(empty, data), data)); } - function test_Slice_Basic() public pure { + function test_Slice_Basic() public view { bytes memory data = hex"0123456789"; bytes memory expected = hex"234567"; - bytes memory result = data.slice(1, 3); - assertTrue(result.equal(expected)); + bytes memory result = harness.slice(data, 1, 3); + assertTrue(harness.equal(result, expected)); } function test_Revert_SliceOutOfBounds() public { @@ -38,52 +88,52 @@ contract BytesLibUnitTests is Test { // Test start beyond length vm.expectRevert(BytesLib.OutOfBounds.selector); - data.slice(10, 1); + harness.slice(data, 10, 1); // Test length beyond data bounds vm.expectRevert(BytesLib.OutOfBounds.selector); - data.slice(0, 11); + harness.slice(data, 0, 11); // Test start + length beyond bounds vm.expectRevert(BytesLib.OutOfBounds.selector); - data.slice(5, 6); + harness.slice(data, 5, 6); } - function test_ToAddress() public pure { + function test_ToAddress() public view { bytes memory data = hex"000000000000000000000000A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; address expected = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; - assertEq(data.toAddress(12), expected); + assertEq(harness.toAddress(data, 12), expected); } - function test_ToUint() public pure { + function test_ToUint() public view { bytes memory data = hex"000000000000000000000000000000000000000000000000000000000000002A"; - assertEq(data.toUint256(0), 42); - assertEq(data.toUint8(31), 42); - assertEq(data.toUint16(30), 42); - assertEq(data.toUint32(28), 42); - assertEq(data.toUint64(24), 42); - assertEq(data.toUint96(20), 42); - assertEq(data.toUint128(16), 42); + assertEq(harness.toUint256(data, 0), 42); + assertEq(harness.toUint8(data, 31), 42); + assertEq(harness.toUint16(data, 30), 42); + assertEq(harness.toUint32(data, 28), 42); + assertEq(harness.toUint64(data, 24), 42); + assertEq(harness.toUint96(data, 20), 42); + assertEq(harness.toUint128(data, 16), 42); } - function test_Equal() public pure { + function test_Equal() public view { bytes memory a = hex"1234567890"; bytes memory b = hex"1234567890"; bytes memory c = hex"1234567891"; - assertTrue(a.equal(b)); - assertFalse(a.equal(c)); + assertTrue(harness.equal(a, b)); + assertFalse(harness.equal(a, c)); } function test_Revert_ToTypesOutOfBounds() public { bytes memory tooShort = hex"1234"; vm.expectRevert(BytesLib.OutOfBounds.selector); - tooShort.toAddress(0); + harness.toAddress(tooShort, 0); vm.expectRevert(BytesLib.OutOfBounds.selector); - tooShort.toUint256(0); + harness.toUint256(tooShort, 0); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/test/unit/libraries/SnapshotsUnit.t.sol b/src/test/unit/libraries/SnapshotsUnit.t.sol index 7b43b5017..086241c09 100644 --- a/src/test/unit/libraries/SnapshotsUnit.t.sol +++ b/src/test/unit/libraries/SnapshotsUnit.t.sol @@ -4,76 +4,116 @@ pragma solidity ^0.8.27; import "forge-std/Test.sol"; import "src/contracts/libraries/Snapshots.sol"; -contract SnapshotsUnitTests is Test { +contract SnapshotsHarness { using Snapshots for Snapshots.DefaultWadHistory; using Snapshots for Snapshots.DefaultZeroHistory; - Snapshots.DefaultWadHistory history; - Snapshots.DefaultZeroHistory zeroHistory; + Snapshots.DefaultWadHistory internal wadHistory; + Snapshots.DefaultZeroHistory internal zeroHistory; + + function pushWad(uint32 key, uint64 value) public { + wadHistory.push(key, value); + } + + function upperLookupWad(uint32 key) public view returns (uint64) { + return wadHistory.upperLookup(key); + } + + function latestWad() public view returns (uint64) { + return wadHistory.latest(); + } + + function lengthWad() public view returns (uint256) { + return wadHistory.length(); + } + + function pushZero(uint32 key, uint256 value) public { + zeroHistory.push(key, value); + } + + function upperLookupZero(uint32 key) public view returns (uint256) { + return zeroHistory.upperLookup(key); + } + + function latestZero() public view returns (uint256) { + return zeroHistory.latest(); + } + + function lengthZero() public view returns (uint256) { + return zeroHistory.length(); + } +} + +contract SnapshotsUnitTests is Test { + SnapshotsHarness harness; + + function setUp() public { + harness = new SnapshotsHarness(); + } function test_Revert_InvalidSnapshotOrdering(uint256 r) public { uint32 key = uint32(bound(r, 1, type(uint32).max)); uint32 smallerKey = uint32(bound(r, 0, key - 1)); - history.push(key, 1); + harness.pushWad(key, 1); vm.expectRevert(Snapshots.InvalidSnapshotOrdering.selector); - history.push(smallerKey, 2); + harness.pushWad(smallerKey, 2); } function test_Push_Correctness(uint256 r) public { uint32 key = uint32(bound(r, 0, type(uint32).max)); uint64 value = uint32(bound(r, 0, type(uint64).max)); - history.push(key, value); + harness.pushWad(key, value); - assertEq(history.upperLookup(key), value); - assertEq(history.latest(), value); - assertEq(history.length(), 1); + assertEq(harness.upperLookupWad(key), value); + assertEq(harness.latestWad(), value); + assertEq(harness.lengthWad(), 1); } function test_UpperLookup_InitiallyWad(uint32 r) public view { - assertEq(history.upperLookup(r), 1e18); + assertEq(harness.upperLookupWad(r), 1e18); } function test_Latest_InitiallyWad() public view { - assertEq(history.latest(), 1e18); + assertEq(harness.latestWad(), 1e18); } function test_Length_InitiallyZero() public view { - assertEq(history.length(), 0); + assertEq(harness.lengthWad(), 0); } function test_Revert_InvalidSnapshotOrdering_ZeroHistory(uint256 r) public { uint32 key = uint32(bound(r, 1, type(uint32).max)); uint32 smallerKey = uint32(bound(r, 0, key - 1)); - zeroHistory.push(key, 1); + harness.pushZero(key, 1); vm.expectRevert(Snapshots.InvalidSnapshotOrdering.selector); - zeroHistory.push(smallerKey, 2); + harness.pushZero(smallerKey, 2); } function test_Push_Correctness_ZeroHistory(uint256 r) public { uint32 key = uint32(bound(r, 0, type(uint32).max)); uint256 value = bound(r, 0, type(uint224).max); - zeroHistory.push(key, value); + harness.pushZero(key, value); - assertEq(zeroHistory.upperLookup(key), value); - assertEq(zeroHistory.latest(), value); - assertEq(zeroHistory.length(), 1); + assertEq(harness.upperLookupZero(key), value); + assertEq(harness.latestZero(), value); + assertEq(harness.lengthZero(), 1); } function test_UpperLookup_InitiallyZero(uint32 r) public view { - assertEq(zeroHistory.upperLookup(r), 0); + assertEq(harness.upperLookupZero(r), 0); } function test_Latest_InitiallyZero() public view { - assertEq(zeroHistory.latest(), 0); + assertEq(harness.latestZero(), 0); } function test_Length_InitiallyZero_ZeroHistory() public view { - assertEq(zeroHistory.length(), 0); + assertEq(harness.lengthZero(), 0); } } \ No newline at end of file diff --git a/src/test/unit/mixins/SignatureUtilsUnit.t.sol b/src/test/unit/mixins/SignatureUtilsUnit.t.sol index 6f998923f..645a2d9ec 100644 --- a/src/test/unit/mixins/SignatureUtilsUnit.t.sol +++ b/src/test/unit/mixins/SignatureUtilsUnit.t.sol @@ -16,7 +16,24 @@ contract MockSigner { } } -contract SignatureUtilsUnit is Test, SignatureUtils { +contract SignatureUtilsHarness is SignatureUtils { + function calculateSignableDigest(bytes32 hash) public view returns (bytes32) { + return _calculateSignableDigest(hash); + } + + function checkIsValidSignatureNow( + address signer, + bytes32 digest, + bytes memory signature, + uint256 expiry + ) public view { + _checkIsValidSignatureNow(signer, digest, signature, expiry); + } +} + +contract SignatureUtilsUnit is Test { + SignatureUtilsHarness harness; + MockSigner mockSigner; uint256 signerPk; address signer; bytes32 hash; @@ -26,35 +43,36 @@ contract SignatureUtilsUnit is Test, SignatureUtils { function setUp() public { vm.chainId(1); + harness = new SignatureUtilsHarness(); + mockSigner = new MockSigner(); signerPk = 1; signer = vm.addr(signerPk); hash = keccak256(""); - digest = _calculateSignableDigest(hash); + digest = harness.calculateSignableDigest(hash); expectedDomainSeparator = keccak256( abi.encode( keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"), keccak256(bytes("EigenLayer")), block.chainid, - address(this) + address(harness) ) ); } function test_domainSeparator_NonZero() public view { - assertTrue(_INITIAL_DOMAIN_SEPARATOR != 0, "The initial domain separator should be non-zero"); - assertTrue(domainSeparator() != 0, "The domain separator should be non-zero"); - assertTrue(domainSeparator() == expectedDomainSeparator, "The domain separator should be as expected"); + assertTrue(harness.domainSeparator() != 0, "The domain separator should be non-zero"); + assertTrue(harness.domainSeparator() == expectedDomainSeparator, "The domain separator should be as expected"); } function test_domainSeparator_NewChainId() public { - bytes32 initialDomainSeparator = domainSeparator(); + bytes32 initialDomainSeparator = harness.domainSeparator(); // Change the chain ID vm.chainId(9999); - bytes32 newDomainSeparator = domainSeparator(); + bytes32 newDomainSeparator = harness.domainSeparator(); assertTrue(newDomainSeparator != 0, "The new domain separator should be non-zero"); assertTrue( @@ -67,10 +85,11 @@ contract SignatureUtilsUnit is Test, SignatureUtils { (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPk, digest); vm.expectRevert(ISignatureUtils.SignatureExpired.selector); - _checkIsValidSignatureNow(signer, digest, abi.encode(r, s, v), block.timestamp - 1); + harness.checkIsValidSignatureNow(signer, digest, abi.encode(r, s, v), block.timestamp - 1); } - function testFail_checkIsValidSignatureNow_InvalidSignature() public view { - _checkIsValidSignatureNow(signer, digest, "", block.timestamp); + function test_Revert_checkIsValidSignatureNow_InvalidSignature() public { + vm.expectRevert(ISignatureUtils.InvalidSignature.selector); + harness.checkIsValidSignatureNow(signer, digest, "", block.timestamp); } } \ No newline at end of file