diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index 837cd50..6b2b2ea 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -30,6 +30,12 @@ jobs: export PATH="/home/runner/.config/.foundry/bin:$PATH"; make build; + - name: Verify storage layout + run: | + git fetch origin main || true + export PATH="/home/runner/.config/.foundry/bin:$PATH"; + make verify-storage-layout; + - name: Run tests run: | export PATH="/home/runner/.config/.foundry/bin:$PATH"; diff --git a/Makefile b/Makefile index 6b41476..f64ed1a 100644 --- a/Makefile +++ b/Makefile @@ -54,4 +54,16 @@ extract-abis: .PHONY: contract-size-check contract-size-check: @echo "Checking contract sizes..." - bash tools/check-contract-size.sh \ No newline at end of file + bash tools/check-contract-size.sh + +# Generate storage layout +.PHONY: gen +gen: + @echo "Generating storage layout..." + bash tools/gen-storage-layout.sh + +# Verify storage layout (for CI) +.PHONY: verify-storage-layout +verify-storage-layout: + @echo "Verifying storage layout..." + bash tools/verify-storage-layout.sh \ No newline at end of file diff --git a/lib/pyth-sdk-solidity b/lib/pyth-sdk-solidity index 11d6bcf..d7dd6e1 160000 --- a/lib/pyth-sdk-solidity +++ b/lib/pyth-sdk-solidity @@ -1 +1 @@ -Subproject commit 11d6bcfc2e56885535a9a8e3c8417847cb20be14 +Subproject commit d7dd6e149936552198c12fac1273997cefc03ceb diff --git a/src/PDPVerifier.sol b/src/PDPVerifier.sol index db625c3..4532a3f 100644 --- a/src/PDPVerifier.sol +++ b/src/PDPVerifier.sol @@ -961,9 +961,8 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable { address listenerAddr = dataSetListener[setId]; if (listenerAddr != address(0)) { - PDPListener(listenerAddr).nextProvingPeriod( - setId, nextChallengeEpoch[setId], dataSetLeafCount[setId], extraData - ); + PDPListener(listenerAddr) + .nextProvingPeriod(setId, nextChallengeEpoch[setId], dataSetLeafCount[setId], extraData); } emit NextProvingPeriod(setId, challengeEpoch, dataSetLeafCount[setId]); } diff --git a/src/PDPVerifierServiceLayout.sol b/src/PDPVerifierServiceLayout.sol new file mode 100644 index 0000000..c0cbf84 --- /dev/null +++ b/src/PDPVerifierServiceLayout.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +pragma solidity ^0.8.20; + +/** + * @title PDPVerifierServiceLayout + * @notice This file defines the storage layout for PDPVerifier. + * @dev This file is auto-generated by `make gen`. DO NOT EDIT MANUALLY. + * This contract should NOT be deployed or used directly; it is only for storage slot tracking. + */ +abstract contract PDPVerifierServiceLayout { + /** + * @dev STORAGE LAYOUT (slot: type name) + * [slot: 0] uint256 challengeFinality + * [slot: 1] uint64 nextDataSetId + * [slot: 2] mapping(uint256 => mapping(uint256 => struct Cids.Cid)) pieceCids + * [slot: 3] mapping(uint256 => mapping(uint256 => uint256)) pieceLeafCounts + * [slot: 4] mapping(uint256 => mapping(uint256 => uint256)) sumTreeCounts + * [slot: 5] mapping(uint256 => uint256) nextPieceId + * [slot: 6] mapping(uint256 => uint256) dataSetLeafCount + * [slot: 7] mapping(uint256 => uint256) nextChallengeEpoch + * [slot: 8] mapping(uint256 => address) dataSetListener + * [slot: 9] mapping(uint256 => uint256) challengeRange + * [slot: 10] mapping(uint256 => uint256[]) scheduledRemovals + * [slot: 11] mapping(uint256 => mapping(uint256 => uint256)) scheduledRemovalsBitmap + * [slot: 12] mapping(uint256 => address) storageProvider + * [slot: 13] mapping(uint256 => address) dataSetProposedStorageProvider + * [slot: 14] mapping(uint256 => uint256) dataSetLastProvenEpoch + * [slot: 15] struct PDPVerifier.FeeStatus feeStatus + * [slot: 16] struct PDPVerifier.PlannedUpgrade nextUpgrade + */ +} diff --git a/src/SimplePDPService.sol b/src/SimplePDPService.sol index 1b79e9c..80bc656 100644 --- a/src/SimplePDPService.sol +++ b/src/SimplePDPService.sol @@ -221,7 +221,10 @@ contract SimplePDPService is PDPListener, IPDPProvingSchedule, Initializable, UU uint256, /*challengedLeafCount*/ uint256, /*seed*/ uint256 challengeCount - ) external onlyPDPVerifier { + ) + external + onlyPDPVerifier + { if (provenThisPeriod[dataSetId]) { revert("Only one proof of possession allowed per proving period. Open a new proving period."); } @@ -253,7 +256,10 @@ contract SimplePDPService is PDPListener, IPDPProvingSchedule, Initializable, UU uint256, /*leafCount*/ bytes calldata - ) external onlyPDPVerifier { + ) + external + onlyPDPVerifier + { // initialize state for new data set if (provingDeadlines[dataSetId] == NO_PROVING_DEADLINE) { uint256 firstDeadline = block.number + getMaxProvingPeriod(); diff --git a/test/PDPVerifier.t.sol b/test/PDPVerifier.t.sol index 805b4d9..a6d70c5 100644 --- a/test/PDPVerifier.t.sol +++ b/test/PDPVerifier.t.sol @@ -1027,8 +1027,7 @@ contract PDPVerifierPaginationTest is MockFVMTest, PieceHelper { assertEq(pdpVerifier.getActivePieceCount(setId), 5, "Should have 5 active pieces"); // Test offset beyond range - (Cids.Cid[] memory pieces1, /*uint256[] memory ids1*/, bool hasMore1) = - pdpVerifier.getActivePieces(setId, 10, 5); + (Cids.Cid[] memory pieces1,/*uint256[] memory ids1*/, bool hasMore1) = pdpVerifier.getActivePieces(setId, 10, 5); assertEq(pieces1.length, 0, "Should return empty when offset beyond range"); assertEq(hasMore1, false, "Should not have more items"); diff --git a/test/SimplePDPService.t.sol b/test/SimplePDPService.t.sol index e4a3edc..416d522 100644 --- a/test/SimplePDPService.t.sol +++ b/test/SimplePDPService.t.sol @@ -384,8 +384,12 @@ contract SimplePDPServiceFaultsTest is Test { } function testGetPDPConfig() public view { - (uint64 maxProvingPeriod, uint256 challengeWindow, uint256 challengesPerProof, uint256 initChallengeWindowStart) - = pdpService.getPDPConfig(); + ( + uint64 maxProvingPeriod, + uint256 challengeWindow, + uint256 challengesPerProof, + uint256 initChallengeWindowStart + ) = pdpService.getPDPConfig(); assertEq(maxProvingPeriod, 2880, "Max proving period should be 2880"); assertEq(challengeWindow, 60, "Challenge window should be 60"); diff --git a/tools/gen-storage-layout.sh b/tools/gen-storage-layout.sh new file mode 100755 index 0000000..61aee50 --- /dev/null +++ b/tools/gen-storage-layout.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +# This script generates the storage layout for PDPVerifier +# It uses forge inspect to get the storage layout in a table format +# and then formats it into a Solidity file for tracking. + +CONTRACT="PDPVerifier" +OUTPUT_FILE="src/${CONTRACT}ServiceLayout.sol" + +echo "Generating storage layout for ${CONTRACT}..." + +# Get storage layout from forge and parse the table format +LAYOUT_CONTENT=$(forge inspect ${CONTRACT} storage-layout | awk -F'|' '/src\/'${CONTRACT}'\.sol/ { gsub(/^[ \t]+|[ \t]+$/, "", $2); gsub(/^[ \t]+|[ \t]+$/, "", $3); gsub(/^[ \t]+|[ \t]+$/, "", $4); print " * [slot: " $4 "] " $3 " " $2 }') + +# Generate Solidity file +cat << SOL_EOF > "${OUTPUT_FILE}" +// SPDX-License-Identifier: Apache-2.0 OR MIT +pragma solidity ^0.8.20; + +/** + * @title ${CONTRACT}ServiceLayout + * @notice This file defines the storage layout for ${CONTRACT}. + * @dev This file is auto-generated by \`make gen\`. DO NOT EDIT MANUALLY. + * This contract should NOT be deployed or used directly; it is only for storage slot tracking. + */ +abstract contract ${CONTRACT}ServiceLayout { + /** + * @dev STORAGE LAYOUT (slot: type name) +${LAYOUT_CONTENT} + */ +} +SOL_EOF + +echo "Storage layout generated at ${OUTPUT_FILE}" diff --git a/tools/verify-storage-layout.sh b/tools/verify-storage-layout.sh new file mode 100755 index 0000000..bc04ff5 --- /dev/null +++ b/tools/verify-storage-layout.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +set -euo pipefail + +# This script verifies that the checked-in storage layout matches the freshly generated one +# and ensures that only additions are made to the storage layout when compared to the base branch. + +CONTRACT="PDPVerifier" +LAYOUT_FILE="src/${CONTRACT}ServiceLayout.sol" +TMP_LAYOUT_FILE="src/${CONTRACT}ServiceLayout.sol.tmp" + +echo "Verifying storage layout for ${CONTRACT}..." + +GEN_SCRIPT="tools/gen-storage-layout.sh" +if [ ! -f "$GEN_SCRIPT" ]; then + echo "Error: Gen script not found at $GEN_SCRIPT" + exit 1 +fi + +# Run gen script which outputs to the actual LAYOUT_FILE, +# but we first move the original aside so we can compare it! +if [ -f "$LAYOUT_FILE" ]; then + cp "$LAYOUT_FILE" "${LAYOUT_FILE}.bak" +else + # If the file didn't exist at all locally, just create an empty backup + touch "${LAYOUT_FILE}.bak" +fi + +# Generate fresh layout +bash "$GEN_SCRIPT" +mv "$LAYOUT_FILE" "$TMP_LAYOUT_FILE" + +# Restore original file for comparison +if [ -s "${LAYOUT_FILE}.bak" ]; then + mv "${LAYOUT_FILE}.bak" "$LAYOUT_FILE" +else + rm "${LAYOUT_FILE}.bak" + touch "$LAYOUT_FILE" +fi + +# 1. Check if files match +# (Compare locally checked-in layout vs what make gen produces) +if [ ! -s "$LAYOUT_FILE" ]; then + echo "Error: Checked-in storage layout does not exist. Please run 'make gen' and commit." + rm -f "$TMP_LAYOUT_FILE" + exit 1 +fi + +if ! diff -q "$LAYOUT_FILE" "$TMP_LAYOUT_FILE" > /dev/null; then + echo "Error: Checked-in storage layout does not match freshly generated one!" + echo "Please run 'make gen' and commit the changes." + diff -u "$LAYOUT_FILE" "$TMP_LAYOUT_FILE" || true + rm "$TMP_LAYOUT_FILE" + exit 1 +fi + +# 2. Check for destructive changes (only additions allowed vs base branch) +BASE_BRANCH=${GITHUB_BASE_REF:-main} +BASE_LAYOUT_FILE="src/${CONTRACT}ServiceLayout.sol.base" + +echo "Checking for destructive storage changes against branch: $BASE_BRANCH..." +if git show "origin/$BASE_BRANCH:$LAYOUT_FILE" > "$BASE_LAYOUT_FILE" 2>/dev/null || git show "$BASE_BRANCH:$LAYOUT_FILE" > "$BASE_LAYOUT_FILE" 2>/dev/null; then + OLD_SLOTS=$(grep "\[slot:" "$BASE_LAYOUT_FILE" | sed 's/^[[:space:]]*//') + NEW_SLOTS=$(grep "\[slot:" "$TMP_LAYOUT_FILE" | sed 's/^[[:space:]]*//') + + while IFS= read -r old_line; do + if [ -z "$old_line" ]; then continue; fi + if ! echo "$NEW_SLOTS" | grep -Fqx "$old_line"; then + echo "Error: Destructive storage change detected!" + echo "Missing or modified slot from base branch ($BASE_BRANCH):" + echo " $old_line" + rm "$BASE_LAYOUT_FILE" + rm "$TMP_LAYOUT_FILE" + exit 1 + fi + done <<< "$OLD_SLOTS" + rm "$BASE_LAYOUT_FILE" +else + echo "Base layout not found on $BASE_BRANCH. Skipping destructive change check." +fi + +echo "Storage layout verification passed." +rm "$TMP_LAYOUT_FILE"