diff --git a/l1-contracts/.gitignore b/l1-contracts/.gitignore index 81e472dc0c58..47a827ebbecc 100644 --- a/l1-contracts/.gitignore +++ b/l1-contracts/.gitignore @@ -24,3 +24,6 @@ yarn.lock # 'deploy_contracts' script output serve/ + +gas_report.new.* +gas_report.diff diff --git a/l1-contracts/README.md b/l1-contracts/README.md index 22e941a83263..a2aa32b0933a 100644 --- a/l1-contracts/README.md +++ b/l1-contracts/README.md @@ -12,7 +12,8 @@ Alternatively you can use docker instead, it will handle installations and run t The `src` folder contain contracts that is to be used by the local developer testnet. It is grouped into 3 categories: -- `core` contains the required contracts, the bare minimum +- `core` contains the required contracts, the bare minimum. +- `governance` contains the contracts for the governance system. - `mock` contains stubs, for now an always true verifier. - `periphery` stuff that is nice to have, convenience contracts and functions belong in here. @@ -31,6 +32,29 @@ We use `forge fmt` to format. But follow a few general guidelines beyond the sta - Do `function transfer(address _to, uint256 _amount);` - use `_` prefix for `internal` and `private` functions. +## Gas Reports + +You can run `./bootstrap.sh gas_report` to generate a detailed gas report for the current state and update the gas_report.md file. + +When running CI or tests with `./bootstrap.sh test`, the script will automatically check if gas usage has changed by running `./bootstrap.sh gas_report check`. If gas usage has changed, the test will fail and show a diff of the changes. + +If the changes in gas usage are expected and desired: + +1. Review the diff shown in the output +2. Run `./bootstrap.sh gas_report` to update the gas report file +3. Commit the updated gas_report.md file + +NOTE: Our gas reporting excludes certain tests due to Forge limitations: + +- FeeRollupTest and MinimalFeeModelTest test suites are excluded +- testInvalidBlobHash and testInvalidBlobProof test cases are excluded + +This is related to [this Foundry issue](https://github.com/foundry-rs/foundry/issues/10074). + +This means that we don't report gas for blob validation (currently 50k gas per blob, and we use 3 blobs per propose in production). + +If you want to run gas reports directly with `forge`, you must use the environment variable `FORGE_GAS_REPORT=true` instead of the `--gas-report` flag. The `./bootstrap.sh gas_report` command does this for you automatically. + ## Contracts: The contracts are in a very early stage, and don't worry about gas costs right now. Instead they prioritize development velocity. diff --git a/l1-contracts/bootstrap.sh b/l1-contracts/bootstrap.sh index 0e07575dbf4d..26a3e6d6b95a 100755 --- a/l1-contracts/bootstrap.sh +++ b/l1-contracts/bootstrap.sh @@ -53,7 +53,7 @@ function build { function test_cmds { echo "$hash cd l1-contracts && solhint --config ./.solhint.json \"src/**/*.sol\"" echo "$hash cd l1-contracts && forge fmt --check" - echo "$hash cd l1-contracts && forge test --no-match-contract UniswapPortalTest" + echo "$hash cd l1-contracts && forge test && ./bootstrap.sh gas_report" } function test { @@ -100,6 +100,29 @@ function inspect { done } +function gas_report { + check=${1:-"no"} + echo_header "l1-contracts gas report" + forge --version + + FORGE_GAS_REPORT=true forge test \ + --no-match-contract "(FeeRollupTest)|(MinimalFeeModelTest)|(UniswapPortalTest)" \ + --no-match-test "(testInvalidBlobHash)|(testInvalidBlobProof)" \ + --fuzz-seed 42 \ + --isolate \ + > gas_report.new.tmp + grep "^|" gas_report.new.tmp > gas_report.new.md + rm gas_report.new.tmp + diff gas_report.new.md gas_report.md > gas_report.diff || true + + if [ -s gas_report.diff -a "$check" = "check" ]; then + cat gas_report.diff + echo "Gas report has changed. Please check the diffs above, then run './bootstrap.sh gas_report' to update the gas report." + exit 1 + fi + mv gas_report.new.md gas_report.md +} + # First argument is a branch name (e.g. master, or the latest version e.g. 1.2.3) to push to the head of. # Second argument is the tag name (e.g. v1.2.3, or commit-). # Third argument is the semver for package.json (e.g. 1.2.3 or 1.2.3-commit.) @@ -195,6 +218,10 @@ case "$cmd" in "inspect") inspect ;; + "gas_report") + shift + gas_report "$@" + ;; test_cmds|release) $cmd ;; diff --git a/l1-contracts/foundry.toml b/l1-contracts/foundry.toml index 8793b237b177..2ed25d70dbbe 100644 --- a/l1-contracts/foundry.toml +++ b/l1-contracts/foundry.toml @@ -4,6 +4,9 @@ out = 'out' libs = ['lib'] solc = "0.8.27" evm_version = 'cancun' +# Helper to get all the contract names in the src/governance and src/core directories +# find ./src/governance ./src/core ./src/periphery/Forwarder -type f -name "*.sol" -exec grep -h "^contract [A-Za-z]" {} \; | sed -E 's/contract ([A-Za-z0-9_]+).*/"\1"/' | tr "\n" ", " +gas_reports = ["Governance","Registry","RewardDistributor","GovernanceProposer","CoinIssuer","Rollup","Inbox","Outbox","FeeJuicePortal","RollupCore","SlashingProposer","Slasher","Forwarder"] remappings = [ "@oz/=lib/openzeppelin-contracts/contracts/", @@ -29,4 +32,5 @@ tab_width = 2 variable_override_spacing=false [rpc_endpoints] -mainnet_fork="https://mainnet.infura.io/v3/9928b52099854248b3a096be07a6b23c" \ No newline at end of file +mainnet_fork="https://mainnet.infura.io/v3/9928b52099854248b3a096be07a6b23c" + diff --git a/l1-contracts/gas_report.md b/l1-contracts/gas_report.md new file mode 100644 index 000000000000..ed4b2d8557df --- /dev/null +++ b/l1-contracts/gas_report.md @@ -0,0 +1,164 @@ +| src/core/FeeJuicePortal.sol:FeeJuicePortal contract | | | | | | +|-----------------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 589795 | 2886 | | | | | +| Function Name | min | avg | median | max | # calls | +| L2_TOKEN_ADDRESS | 194 | 194 | 194 | 194 | 256 | +| UNDERLYING | 270 | 270 | 270 | 270 | 3064 | +| canonicalRollup | 1016 | 3624 | 5516 | 5516 | 5550 | +| depositToAztecPublic | 42745 | 127319 | 127958 | 127958 | 258 | +| distributeFees | 27333 | 56798 | 57006 | 57006 | 258 | +| initialize | 48963 | 48963 | 48963 | 48963 | 1566 | +| src/core/Rollup.sol:Rollup contract | | | | | | +|-------------------------------------|-----------------|---------|---------|----------|---------| +| Deployment Cost | Deployment Size | | | | | +| 7993984 | 38372 | | | | | +| Function Name | min | avg | median | max | # calls | +| archive | 605 | 605 | 605 | 605 | 2474 | +| cheat__InitialiseValidatorSet | 751841 | 8111188 | 751865 | 15671744 | 519 | +| claimProverRewards | 31816 | 53337 | 34261 | 93936 | 3 | +| claimSequencerRewards | 57196 | 57196 | 57196 | 57196 | 1 | +| deposit | 169711 | 325702 | 342585 | 342585 | 256 | +| getAttesters | 1970 | 26343 | 26629 | 26629 | 259 | +| getBlock | 1230 | 1230 | 1230 | 1230 | 886 | +| getCollectiveProverRewardsForEpoch | 636 | 1636 | 1636 | 2636 | 4 | +| getCurrentEpoch | 908 | 908 | 908 | 908 | 1032 | +| getCurrentEpochCommittee | 2962 | 2962 | 2962 | 2962 | 1 | +| getCurrentProposer | 3182 | 722366 | 9153 | 2285955 | 815 | +| getCurrentSlot | 713 | 1332 | 713 | 4713 | 142 | +| getEpochCommittee | 2010 | 14068 | 14218 | 14218 | 520 | +| getEpochDuration | 439 | 439 | 439 | 439 | 256 | +| getFeeAssetPerEth | 1445 | 1445 | 1445 | 1445 | 1 | +| getHasSubmitted | 942 | 1192 | 942 | 2942 | 8 | +| getInbox | 476 | 581 | 476 | 2476 | 4926 | +| getInfo | 1527 | 1527 | 1527 | 1527 | 16 | +| getManaBaseFeeAt | 7834 | 15768 | 16535 | 16545 | 2333 | +| getOutbox | 518 | 895 | 518 | 2518 | 5434 | +| getPendingBlockNumber | 507 | 507 | 507 | 507 | 1546 | +| getProofSubmissionWindow | 404 | 404 | 404 | 404 | 4 | +| getProvenBlockNumber | 512 | 784 | 512 | 2512 | 7551 | +| getProvingCostPerManaInEth | 429 | 429 | 429 | 429 | 1 | +| getProvingCostPerManaInFeeAsset | 4147 | 4147 | 4147 | 4147 | 1 | +| getSequencerRewards | 671 | 1071 | 671 | 2671 | 5 | +| getSlasher | 518 | 518 | 518 | 518 | 518 | +| getSlotDuration | 443 | 443 | 443 | 443 | 256 | +| getSpecificProverRewardsForEpoch | 822 | 2130 | 1634 | 3634 | 5 | +| getTargetCommitteeSize | 462 | 462 | 462 | 462 | 768 | +| getTimestampForSlot | 909 | 910 | 909 | 4909 | 2462 | +| propose | 129177 | 377904 | 379855 | 589151 | 2601 | +| prune | 25642 | 36116 | 37377 | 41862 | 6 | +| setProvingCostPerMana | 28713 | 28713 | 28713 | 28713 | 2 | +| setupEpoch | 208063 | 3476415 | 3553018 | 3553018 | 262 | +| submitEpochRootProof | 64866 | 426520 | 424623 | 456356 | 885 | +| src/core/messagebridge/Inbox.sol:Inbox contract | | | | | | +|-------------------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 0 | 0 | | | | | +| Function Name | min | avg | median | max | # calls | +| getRoot | 802 | 802 | 802 | 802 | 2 | +| inProgress | 304 | 304 | 304 | 304 | 3 | +| sendL2Message | 44403 | 54747 | 47796 | 95703 | 41504 | +| totalMessagesInserted | 284 | 284 | 284 | 284 | 512 | +| src/core/messagebridge/Outbox.sol:Outbox contract | | | | | | +|---------------------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 586673 | 2646 | | | | | +| Function Name | min | avg | median | max | # calls | +| consume | 28894 | 72013 | 73138 | 73400 | 4262 | +| getRootData | 940 | 1363 | 1171 | 3217 | 2732 | +| hasMessageBeenConsumedAtBlockAndIndex | 591 | 2583 | 2591 | 2591 | 259 | +| insert | 22188 | 57508 | 68264 | 68264 | 1097 | +| src/core/staking/Slasher.sol:Slasher contract | | | | | | +|-----------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 0 | 0 | | | | | +| Function Name | min | avg | median | max | # calls | +| PROPOSER | 182 | 182 | 182 | 182 | 2 | +| slash | 125484 | 125484 | 125484 | 125484 | 1 | +| src/core/staking/SlashingProposer.sol:SlashingProposer contract | | | | | | +|-----------------------------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 0 | 0 | | | | | +| Function Name | min | avg | median | max | # calls | +| executeProposal | 139987 | 139987 | 139987 | 139987 | 1 | +| vote | 63905 | 75272 | 63905 | 120740 | 10 | +| src/governance/CoinIssuer.sol:CoinIssuer contract | | | | | | +|---------------------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 326421 | 1465 | | | | | +| Function Name | min | avg | median | max | # calls | +| RATE | 239 | 239 | 239 | 239 | 768 | +| mint | 23901 | 43841 | 26637 | 81105 | 768 | +| mintAvailable | 503 | 503 | 503 | 503 | 1283 | +| timeOfLastMint | 360 | 360 | 360 | 360 | 256 | +| src/governance/Governance.sol:Governance contract | | | | | | +|---------------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 2332350 | 10841 | | | | | +| Function Name | min | avg | median | max | # calls | +| deposit | 27898 | 171720 | 186529 | 188452 | 9729 | +| dropProposal | 23739 | 40533 | 33600 | 63600 | 2307 | +| execute | 26209 | 71290 | 71327 | 161717 | 3076 | +| finaliseWithdraw | 23757 | 45218 | 48283 | 65383 | 6060 | +| getConfiguration | 1913 | 12163 | 19913 | 19913 | 5396 | +| getProposal | 3523 | 8023 | 3523 | 31523 | 10590 | +| getProposalState | 469 | 11470 | 13558 | 21242 | 23311 | +| getWithdrawal | 1075 | 1075 | 1075 | 1075 | 10134 | +| governanceProposer | 424 | 1418 | 424 | 2424 | 515 | +| initiateWithdraw | 30945 | 199224 | 211342 | 228958 | 7555 | +| powerAt | 1042 | 1412 | 1042 | 3029 | 4608 | +| proposalCount | 338 | 1714 | 2338 | 2338 | 1116 | +| propose | 23763 | 321926 | 320487 | 337587 | 606 | +| proposeWithLock | 26545 | 421005 | 422627 | 422627 | 257 | +| totalPowerAt | 612 | 1569 | 883 | 3568 | 6064 | +| updateConfiguration | 23457 | 32910 | 24180 | 48186 | 6145 | +| updateGovernanceProposer | 21705 | 27184 | 28016 | 28028 | 2048 | +| vote | 30670 | 87817 | 94478 | 94500 | 12289 | +| withdrawalCount | 383 | 391 | 383 | 2383 | 2479 | +| src/governance/Registry.sol:Registry contract | | | | | | +|-----------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 500615 | 2063 | | | | | +| Function Name | min | avg | median | max | # calls | +| getCurrentSnapshot | 664 | 2664 | 2664 | 4664 | 514 | +| getGovernance | 341 | 2159 | 2341 | 2341 | 2829 | +| getRollup | 374 | 2358 | 2374 | 2374 | 874093 | +| getSnapshot | 4740 | 4740 | 4740 | 4740 | 257 | +| getVersionFor | 743 | 3527 | 2927 | 4927 | 773 | +| isRollupRegistered | 657 | 3805 | 2812 | 4812 | 515 | +| numberOfVersions | 350 | 1685 | 2350 | 2350 | 770 | +| transferOwnership | 28592 | 28592 | 28592 | 28592 | 106 | +| upgrade | 23672 | 103303 | 106801 | 106801 | 6160 | +| src/governance/RewardDistributor.sol:RewardDistributor contract | | | | | | +|-----------------------------------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 513664 | 2360 | | | | | +| Function Name | min | avg | median | max | # calls | +| BLOCK_REWARD | 238 | 238 | 238 | 238 | 380 | +| canonicalRollup | 1143 | 3143 | 3143 | 5643 | 880 | +| claim | 30122 | 45862 | 35599 | 64024 | 513 | +| owner | 2384 | 2384 | 2384 | 2384 | 257 | +| registry | 347 | 1347 | 1347 | 2347 | 2 | +| updateRegistry | 23757 | 23781 | 23757 | 30119 | 257 | +| src/governance/proposer/GovernanceProposer.sol:GovernanceProposer contract | | | | | | +|----------------------------------------------------------------------------|-----------------|-------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 639517 | 3151 | | | | | +| Function Name | min | avg | median | max | # calls | +| LIFETIME_IN_ROUNDS | 216 | 216 | 216 | 216 | 512 | +| M | 261 | 261 | 261 | 261 | 4868 | +| N | 260 | 260 | 260 | 260 | 1949 | +| REGISTRY | 205 | 205 | 205 | 205 | 256 | +| computeRound | 435 | 435 | 435 | 435 | 266 | +| executeProposal | 29491 | 43507 | 37213 | 366375 | 2053 | +| getExecutor | 3397 | 3397 | 3397 | 3397 | 256 | +| getInstance | 951 | 951 | 951 | 951 | 256 | +| rounds | 865 | 865 | 865 | 865 | 522 | +| vote | 29794 | 50137 | 50072 | 125973 | 859999 | +| yeaCount | 851 | 851 | 851 | 851 | 16 | +| src/periphery/Forwarder.sol:Forwarder contract | | | | | | +|------------------------------------------------|-----------------|-------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 358690 | 1553 | | | | | +| Function Name | min | avg | median | max | # calls | +| forward | 24936 | 27197 | 27012 | 132931 | 514 | diff --git a/l1-contracts/test/base/RollupBase.sol b/l1-contracts/test/base/RollupBase.sol index 6bf8d0b36d34..d5186f09c496 100644 --- a/l1-contracts/test/base/RollupBase.sol +++ b/l1-contracts/test/base/RollupBase.sol @@ -204,7 +204,14 @@ contract RollupBase is DecoderBase { blobHash := mload(add(blobInputs, 0x21)) } blobHashes[0] = blobHash; - vm.blobhashes(blobHashes); + // https://github.com/foundry-rs/foundry/issues/10074 + // don't add blob hashes if forge gas report is true + if (!vm.envOr("FORGE_GAS_REPORT", false)) { + vm.blobhashes(blobHashes); + } else { + // skip blob check if forge gas report is true + skipBlobCheck(address(rollup)); + } } ProposeArgs memory args = ProposeArgs({