Cancel eth tests#136
Conversation
WalkthroughThis update introduces support for a Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant ProofGen as SampleDepositProofGen
participant Bridge as ETHBridge
participant Canceler
participant Recipient
User->>ProofGen: Generate deposit proof (with canceler)
ProofGen-->>User: Deposit proof (includes canceler address)
User->>Bridge: Submit deposit (with canceler)
Bridge-->>User: Emit Deposit event
Canceler->>Bridge: Cancel deposit
alt Canceler is authorized
Bridge-->>Canceler: Emit DepositCancelled event
Bridge-->>Recipient: (No ETH sent)
Bridge-->>Canceler: ETH sent to cancellationRecipient
else Unauthorized
Bridge-->>Canceler: Revert with OnlyCanceler error
end
User->>Bridge: Claim deposit
alt Deposit is cancelled or already claimed
Bridge-->>User: Revert with AlreadyProcessed error
else
Bridge-->>Recipient: ETH sent
Bridge-->>User: Emit DepositClaimed event
end
Possibly related PRs
Suggested reviewers
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
test/ETHBridge/InitialState.t.sol (1)
10-10: Remove unused console import.The console import appears to be unused and was likely added for debugging purposes.
-import {console} from "forge-std/console.sol";
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
offchain/deposit_signal_proof.rs(1 hunks)offchain/sample_deposit_proof.rs(6 hunks)src/protocol/ETHBridge.sol(1 hunks)src/protocol/IETHBridge.sol(1 hunks)test/ETHBridge/CancellableScenarios.t.sol(1 hunks)test/ETHBridge/CrossChainDepositExists.t.sol(1 hunks)test/ETHBridge/EnumeratedScenarios.t.sol(2 hunks)test/ETHBridge/InitialState.t.sol(2 hunks)test/ETHBridge/SampleDepositProof.t.sol(2 hunks)test/ETHBridge/UniversalTest.t.sol(9 hunks)
🔇 Additional comments (14)
test/ETHBridge/CrossChainDepositExists.t.sol (1)
18-19: LGTM! Clean addition of cancellation recipient for testing.The addition of
cancellationRecipientfollows the established pattern and provides the necessary infrastructure for testing deposit cancellation scenarios.src/protocol/ETHBridge.sol (1)
91-91: LGTM! Error name accurately reflects the broader processing semantics.The change from
AlreadyClaimed()toAlreadyProcessed()correctly reflects that deposits can now be either claimed or cancelled, making the error message more semantically accurate.src/protocol/IETHBridge.sol (1)
45-46: LGTM! Interface change properly reflects expanded processing semantics.The error renaming from
AlreadyClaimedtoAlreadyProcessedwith updated documentation correctly captures that deposits can now be either claimed or cancelled.offchain/deposit_signal_proof.rs (1)
40-43: LGTM! Correctly adds canceler parameter to deposit call.The addition of the
zero_cancelerparameter properly aligns with the updated deposit function signature. UsingAddress::ZEROappropriately makes the deposit non-cancellable for this proof generation scenario.test/ETHBridge/UniversalTest.t.sol (1)
26-26: LGTM: Systematic replacement of canceler address parameter.The consistent replacement of
nonCancellableAddresswithcancellerAddressacross all deposit calls properly aligns with the introduction of the cancellation feature. The test logic remains unchanged, ensuring existing behavior is preserved while adding cancellation capability.Also applies to: 31-31, 40-40, 45-45, 55-55, 59-59, 69-69, 73-73, 83-83, 87-87, 96-96, 104-105, 118-118, 123-123, 136-136, 143-143, 148-148, 155-155, 162-162, 174-174
offchain/sample_deposit_proof.rs (4)
23-32: LGTM: Proper addition of canceler parameter to function signature.The
create_deposit_callfunction correctly accepts the newcancelerparameter and incorporates it into the generated deposit call code. The parameter is properly positioned in the function signature and used consistently.Also applies to: 54-54
59-65: LGTM: Appropriate struct extension for canceler field.The
DepositSpecificationstruct is properly extended with thecancelerfield, and all deposit specifications are updated to include the canceler address. The use of a fixed non-zero address is appropriate for testing purposes.Also applies to: 92-92
81-82: LGTM: Proper use of fixed canceler address for testing.The use of a fixed canceler address (
0xf9f5C5411F0bEf1880cE3B051BD14196479764D2) generated from_randomAddress("canceler")is appropriate for testing and ensures consistency across test scenarios.
151-151: LGTM: Correct propagation of canceler address to deposit calls.The canceler address is properly converted to string and passed to the
create_deposit_callfunction, ensuring the canceler information is correctly included in the generated deposit proof code.test/ETHBridge/EnumeratedScenarios.t.sol (2)
4-4: LGTM: Proper import of cancellation test scenario.The import statement correctly brings in the
DepositIsCancellablecontract from the cancellation scenarios, enabling its integration into the enumerated test framework.
45-54: LGTM: Well-structured test contract for cancellable deposits.The
CancelDepositToEOAcontract properly inherits from multiple scenario contracts including the newDepositIsCancellable, creating a comprehensive test case for cancellable deposits to EOAs. ThesetUpfunction correctly overrides and calls parent setup methods.test/ETHBridge/SampleDepositProof.t.sol (2)
41-41: LGTM: Consistent addition of canceler field to all sample deposits.All sample deposits now correctly include the
cancelerfield set to the fixed address0xf9f5C5411F0bEf1880cE3B051BD14196479764D2, ensuring consistency across all test scenarios. Since this is an auto-generated file, the systematic inclusion of the canceler field indicates proper tooling integration.Also applies to: 68-68, 95-95, 122-122, 149-149, 176-176, 203-203, 230-230
252-252: LGTM: Updated state root and block hash constants.The
getStateRoot()andgetBlockHash()functions return updated constant values that correspond to the new proof data structure including the canceler field. This ensures the sample proofs remain valid and consistent.Also applies to: 257-257
test/ETHBridge/CancellableScenarios.t.sol (1)
1-77: LGTM: Comprehensive test suite for deposit cancellation functionality.The
DepositIsCancellablecontract provides excellent test coverage for the cancellation feature, including:
- Successful cancellation by authorized canceller
- Proper state management (setting processed flag)
- Event emission verification
- ETH transfer validation
- Access control enforcement
- Integration with claim functionality
The test structure follows best practices with proper use of test infrastructure (
vm.prank,vm.expectRevert,vm.expectEmit) and comprehensive assertions. The tests ensure both positive scenarios (successful cancellation) and negative scenarios (unauthorized access, double-processing) are properly covered.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
test/ETHBridge/CancelableScenarios.t.sol (1)
90-113: Remove commented out code.The commented code appears to be unrelated to the cancellation functionality and should be removed to keep the file clean.
-// // This contract describes behaviours that should be valid when the deposit is a valid call -// // to a recipient contract, and all other validity conditions are met. -// abstract contract DepositIsValidContractCall is -// RecipientIsAContract, -// BridgeSufficientlyCapitalized, -// DepositIsClaimable -// { -// function setUp() -// public -// virtual -// override(CrossChainDepositExists, RecipientIsAContract, BridgeSufficientlyCapitalized) -// { -// super.setUp(); -// } -// -// function test_claimDeposit_shouldInvokeRecipientContract() public { -// IETHBridge.ETHDeposit memory deposit = sampleDepositProof.getEthDeposit(_depositIdx()); -// bytes memory proof = abi.encode(sampleDepositProof.getDepositSignalProof(_depositIdx())); -// -// vm.expectEmit(); -// emit TransferRecipient.FunctionCalled(); -// bridge.claimDeposit(deposit, HEIGHT, proof); -// } -// }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
offchain/deposit_signal_proof.rs(2 hunks)offchain/sample_deposit_proof.rs(6 hunks)test/ETHBridge/CancelableScenarios.t.sol(1 hunks)test/ETHBridge/EnumeratedScenarios.t.sol(3 hunks)test/ETHBridge/InitialState.t.sol(1 hunks)test/ETHBridge/SampleDepositProof.t.sol(12 hunks)test/ETHBridge/SampleDepositScenarios.t.sol(1 hunks)test/ETHBridge/UniversalTest.t.sol(9 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- offchain/deposit_signal_proof.rs
- test/ETHBridge/UniversalTest.t.sol
- test/ETHBridge/InitialState.t.sol
- offchain/sample_deposit_proof.rs
- test/ETHBridge/EnumeratedScenarios.t.sol
- test/ETHBridge/SampleDepositProof.t.sol
🔇 Additional comments (5)
test/ETHBridge/SampleDepositScenarios.t.sol (1)
56-66: LGTM! Clean implementation of cancellable deposit scenarios.The new abstract contracts follow the established pattern and provide the necessary deposit indices for cancellable scenarios. The implementation is consistent with the existing codebase structure.
test/ETHBridge/CancelableScenarios.t.sol (4)
8-77: Comprehensive test coverage for cancellable deposits.The
DepositIsCancelablecontract provides excellent test coverage for the cancellation functionality:
- Authorization checks (only canceller can cancel)
- State changes (processed flag is set)
- Event emission (
DepositCancelled)- ETH transfer to cancellation recipient
- Prevention of double processing (claim after cancel fails)
The test implementations are well-structured and follow Solidity testing best practices.
79-88: Good coverage for non-cancellable deposits.The
DepositIsNotCancelablecontract correctly tests that cancellation attempts fail when no canceller is set, ensuring proper authorization controls.
40-57: Excellent balance verification in ETH transfer test.The test correctly verifies that:
- ETH is transferred from bridge to cancellation recipient
- Original recipient balance remains unchanged
- Bridge balance is reduced by the deposit amount
This provides solid assurance that the cancellation mechanism works as intended.
59-67: Good test coverage for preventing double processing.The test correctly verifies that claiming a deposit after cancellation fails with
AlreadyProcessederror, ensuring deposits cannot be processed twice.
|
Looks good. A couple of thoughts:
|
0bb60c7 to
23b951b
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
test/ETHBridge/ContractCallValidityScenarios.t.sol (1)
53-54: Fix misleading comment.The comment states "behaviours that should be valid" but the test actually verifies failure behavior (cancellation should fail with
FailedClaim). This is misleading.-// This contract describes behaviours that should be valid when the deposit is a valid call -// to a recipient contract, and all other validity conditions are met. +// This contract describes behaviours when attempting to cancel a deposit that is a valid call +// to a recipient contract. Such cancellation attempts should fail.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/protocol/ETHBridge.sol(2 hunks)src/protocol/IETHBridge.sol(1 hunks)test/ETHBridge/CancelableScenarios.t.sol(1 hunks)test/ETHBridge/ContractCallValidityScenarios.t.sol(2 hunks)test/ETHBridge/EnumeratedScenarios.t.sol(3 hunks)test/ETHBridge/SampleDepositScenarios.t.sol(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- src/protocol/ETHBridge.sol
- src/protocol/IETHBridge.sol
- test/ETHBridge/SampleDepositScenarios.t.sol
- test/ETHBridge/CancelableScenarios.t.sol
- test/ETHBridge/EnumeratedScenarios.t.sol
🔇 Additional comments (3)
test/ETHBridge/ContractCallValidityScenarios.t.sol (3)
4-4: LGTM: Import statement is correct.The import for
DepositIsCancelableis properly structured and aligns with the new cancellation functionality being added.
55-66: LGTM: Contract structure follows established pattern.The inheritance chain and
setUp()function override pattern is consistent with other test contracts in the file.
68-75: No change required:FailedClaimis the correct revert for cancelDepositThe
cancelDepositflow delegates to_sendETH, which does:(bool success, ) = to.call{value: value}(data); require(success, FailedClaim());Any recipient revert (as in this test) bubbles up as
FailedClaim. ExpectingIETHBridge.FailedClaim.selectoris therefore correct.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
test/ETHBridge/EnumeratedScenarios.t.sol (3)
72-81: Fix naming inconsistency.The contract name uses lowercase "with" while the previous contract uses "WithNoCalldata" with uppercase "W". Consider maintaining consistency.
-contract CancelDeposit_withCalldata_ToEOACancelRecipient is +contract CancelDeposit_WithCalldata_ToEOACancelRecipient is
83-83: Fix incorrect comment.The comment describes a scenario about bridge having no ETH, but the actual contract
SimpleDepositToEOA_NoCancelerIsSetinherits fromBridgeSufficientlyCapitalizedand tests non-cancellable deposits.-// Same transfer as above, but the bridge does not have ETH. It should fail. +// Same transfer as above, but no canceler is set. Cancellation attempts should fail.
116-116: Fix grammatical error in comment.The comment has a grammatical error and unclear wording.
-// Not be able to claim a deposit if the recipient to a cancelable deposit is a contract +// Should not be able to claim a deposit if the recipient of a cancelable deposit is a contract
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
test/ETHBridge/ContractCallValidityScenarios.t.sol(2 hunks)test/ETHBridge/EnumeratedScenarios.t.sol(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- test/ETHBridge/ContractCallValidityScenarios.t.sol
🔇 Additional comments (6)
test/ETHBridge/EnumeratedScenarios.t.sol (6)
4-4: LGTM: Proper import of cancellable scenario traits.The import correctly brings in the necessary traits for testing cancellable and non-cancellable deposits.
7-11: LGTM: Comprehensive import of contract call validity scenarios.The imports correctly include the new
CancelableDepositIsValidContractCalltrait alongside existing contract validity traits.
17-17: LGTM: Proper import of cancellable deposit scenarios.The new cancellable deposit scenarios are correctly imported for use in the test contracts.
Also applies to: 20-20
59-69: LGTM: Well-structured test contract for cancellable deposits.The test contract properly inherits from the necessary traits and follows the established pattern for test setup.
84-93: LGTM: Proper test contract for non-cancellable deposits.The test contract correctly inherits from the necessary traits to test the scenario where no canceler is set.
117-124: LGTM: Proper test contract for cancellable deposits to contracts.The test contract correctly inherits from the necessary traits to test cancellation scenarios with contract recipients.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
test/ETHBridge/ContractCallValidityScenarios.t.sol (3)
4-4: Remove duplicate import.The
TransferRecipientimport on line 9 is redundant since it's already imported on line 8.import {REQUIRED_INPUT, RecipientIsAContract, TransferRecipient} from "./RecipientScenarios.t.sol"; -import {TransferRecipient} from "./RecipientScenarios.t.sol";Also applies to: 8-9
53-53: Fix typo in comment."secnario" should be "scenario".
-// This contract describes the secnario where a cancelable deposit is made but the canceler +// This contract describes the scenario where a cancelable deposit is made but the canceler
79-86: Consider clarifying the test assertion.The test name suggests verifying that the recipient contract is not invoked, but the test only checks for a revert. Consider either:
- Renaming to
test_cancelDeposit_shouldRevertWithEmptyCalldata()to match what's actually tested, or- Adding explicit verification that no contract call occurs before the revert
The current logic is correct - when calldata is empty, the operation should revert with
FailedClaim.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/protocol/ETHBridge.sol(2 hunks)src/protocol/IETHBridge.sol(1 hunks)test/ETHBridge/CancelDepositTest.t.sol(1 hunks)test/ETHBridge/CancelableScenarios.t.sol(1 hunks)test/ETHBridge/ContractCallValidityScenarios.t.sol(2 hunks)test/ETHBridge/RecipientScenarios.t.sol(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- test/ETHBridge/RecipientScenarios.t.sol
🚧 Files skipped from review as they are similar to previous changes (3)
- src/protocol/ETHBridge.sol
- src/protocol/IETHBridge.sol
- test/ETHBridge/CancelableScenarios.t.sol
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: in foundry testing, the syntax `vm.expectrevert(errorselector, 0)` is valid and means "expect zero r...
Learnt from: nikeshnazareth
PR: OpenZeppelin/minimal-rollup#134
File: test/ProverManager/CurrentPeriodIsOpenTest.t.sol:0-0
Timestamp: 2025-06-26T01:42:08.393Z
Learning: In Foundry testing, the syntax `vm.expectRevert(ErrorSelector, 0)` is valid and means "expect zero reverts with that specific error message." This is used to assert that a particular error does NOT occur during function execution, while potentially allowing other errors. This is useful for testing that specific validation checks are bypassed in certain conditions.
Applied to files:
test/ETHBridge/CancelDepositTest.t.sol
🔇 Additional comments (4)
test/ETHBridge/CancelDepositTest.t.sol (2)
168-168: LGTM! Error selector update aligns with improved error semantics.The change from
AlreadyClaimed.selectortoAlreadyProcessed.selectorcorrectly reflects the updated error naming that improves consistency across the codebase. The new error name "AlreadyProcessed" is more accurate since both claiming and canceling deposits result in the same processed state.
1-220: Excellent test coverage for cancel deposit functionality.The test suite is well-structured and comprehensive, covering:
- Basic cancellation scenarios with different data payloads
- Authorization validation (only canceler can cancel)
- Event emission verification
- Double-cancellation prevention
- Contract interaction testing with MockReceiver
The tests properly use Foundry utilities and follow good testing practices with clear setup, execution, and verification phases.
test/ETHBridge/ContractCallValidityScenarios.t.sol (2)
55-66: LGTM! Contract structure follows established patterns.The inheritance structure correctly combines cancelable deposit functionality with contract recipient scenarios, and the setUp function properly handles multiple inheritance.
68-77: LGTM! Test logic is sound and well-structured.The test correctly verifies that when a canceler provides non-empty calldata, the recipient contract's function is invoked and the expected event is emitted.
Changes to gas cost
🧾 Summary (10% most significant diffs)
Full diff report 👇
|
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
gas-reports/minimal_inbox_publish.json(1 hunks)offchain/sample_deposit_proof.rs(6 hunks)src/protocol/ETHBridge.sol(1 hunks)src/protocol/IETHBridge.sol(1 hunks)test/ETHBridge/CancelDepositTest.t.sol(1 hunks)test/ETHBridge/CancelableScenarios.t.sol(1 hunks)test/ETHBridge/CrossChainDepositExists.t.sol(1 hunks)test/ETHBridge/InitialState.t.sol(1 hunks)test/ETHBridge/SampleDepositProof.t.sol(14 hunks)test/ETHBridge/UniversalTest.t.sol(9 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
- test/ETHBridge/CancelDepositTest.t.sol
- src/protocol/ETHBridge.sol
- test/ETHBridge/UniversalTest.t.sol
- test/ETHBridge/InitialState.t.sol
- offchain/sample_deposit_proof.rs
- test/ETHBridge/CancelableScenarios.t.sol
- test/ETHBridge/CrossChainDepositExists.t.sol
- src/protocol/IETHBridge.sol
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: in erc20bridge and similar bridge contracts, when deploying bridged tokens, it's important to check ...
Learnt from: nikeshnazareth
PR: OpenZeppelin/minimal-rollup#146
File: src/protocol/ERC20Bridge.sol:129-135
Timestamp: 2025-08-01T09:46:40.439Z
Learning: In ERC20Bridge and similar bridge contracts, when deploying bridged tokens, it's important to check both _processed[id] for the specific initialization and _deployedTokens[key] for the original token address. The _processed check alone is insufficient because token upgrades that change metadata (name, symbol, decimals) would generate different initialization IDs, potentially allowing multiple bridged tokens for the same original token address.
Applied to files:
test/ETHBridge/SampleDepositProof.t.sol
📚 Learning: in provermanager invariant tests, it's acceptable to rely on solidity mapping behavior that returns ...
Learnt from: nikeshnazareth
PR: OpenZeppelin/minimal-rollup#134
File: test/ProverManager/InvariantTest.t.sol:10-20
Timestamp: 2025-06-24T01:29:22.272Z
Learning: In ProverManager invariant tests, it's acceptable to rely on Solidity mapping behavior that returns zero values for non-existent keys when testing that periods are unused. The zero values serve as a proxy to confirm that a period is not initialized, which is the intended behavior for the invariant tests.
Applied to files:
test/ETHBridge/SampleDepositProof.t.sol
🪛 Biome (2.1.2)
gas-reports/minimal_inbox_publish.json
[error] 1-1: Property key must be double quoted
(parse)
[error] 1-2: Property key must be double quoted
(parse)
🔇 Additional comments (4)
test/ETHBridge/SampleDepositProof.t.sol (4)
43-43: Canceler field properly initialized for existing proofs.The systematic addition of
deposit.canceler = address(0x0000000000000000000000000000000000000000);across all existing proofs (0-7) correctly maintains backward compatibility by making these deposits non-cancelable.Also applies to: 72-72, 99-99, 128-128, 157-157, 186-186, 215-215, 242-242
251-276: New proof 8 correctly implements cancelable deposit.The addition of proof 8 with
deposit.canceler = address(0x738b9be4596e37015bA15F17116c9B2eE971c238)properly demonstrates a cancelable deposit scenario. The proof structure follows the established pattern and enables testing of cancellation functionality.
278-305: New proof 9 enables cancelable contract call testing.The addition of proof 9 with the same canceler address as proof 8 but including contract call data (
deposit.data = bytes(hex"9b28f6fb00000000000000000000000000000000000000000000000000000000000004d2")) enables comprehensive testing of cancellation scenarios involving contract interactions.
320-320: Updated constants align with new deposit structure.The updated
getStateRoot()andgetBlockHash()return values correctly reflect the blockchain state that includes the new canceler field in deposit structures.Also applies to: 325-325
| {num_publications:20, | ||
| average_gas_used_publish: 44563} No newline at end of file |
There was a problem hiding this comment.
Fix invalid JSON format.
The JSON file has unquoted property keys which violates JSON specification and will cause parsing errors. Property keys must be enclosed in double quotes.
Apply this diff to fix the JSON formatting:
-{num_publications:20,
-average_gas_used_publish: 44563}
+{
+ "num_publications": 20,
+ "average_gas_used_publish": 44563
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {num_publications:20, | |
| average_gas_used_publish: 44563} | |
| { | |
| "num_publications": 20, | |
| "average_gas_used_publish": 44563 | |
| } |
🧰 Tools
🪛 Biome (2.1.2)
[error] 1-1: Property key must be double quoted
(parse)
[error] 1-2: Property key must be double quoted
(parse)
🤖 Prompt for AI Agents
In gas-reports/minimal_inbox_publish.json at lines 1 to 2, the JSON object keys
are not enclosed in double quotes, which is invalid JSON syntax. To fix this,
enclose all property keys such as num_publications and average_gas_used_publish
in double quotes to comply with JSON standards and avoid parsing errors.
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Chores