celo-org / celo-monorepo

Official repository for core projects comprising the Celo platform
https://celo.org
Apache License 2.0
687 stars 362 forks source link

List core contracts by bytecode size #10988

Closed arthurgousset closed 2 months ago

arthurgousset commented 2 months ago

Context

Discussion and context

  1. Main Slack thread
  2. Previous smaller thread

References:

  1. Smart Contract Size Limit in Solidity: Techniques for Downsizing Contracts and Avoiding Errors | by Amir Doreh | Medium
  2. EIP-170: Contract code size limit

The goal is to know the size of every core contract to see which is above the bytecode limit.

arthurgousset commented 2 months ago

As a quick solution, I used ChatGPT to help me put together a shell script that calculates the size of each currently deployed core contract.

It uses:

  1. Celo CLI (network:contracts) to get all core contracts and their implementation addresses,
  2. Foundry (cast code) to get the bytecode deployed at each core contract implementation address, and
  3. unix helpers to convert the bytecode string into a kilobyte number and write it to a file

Requirements:

  1. Have Foundry installed
  2. Have celocli installed

Steps to reproduce:

  1. Save the get_contract_bytecode_size.sh script
  2. Make it executable: chmod +x get_contract_bytecode_size.sh
  3. Run the script: ./get_contract_bytecode_size.sh

Current script:

#!/bin/bash

# Get the current date and time for the filename
timestamp=$(date +"%Y%m%d_%H%M%S")
OUTPUT_FILE="bytecode_sizes_$timestamp.txt"

# Set the RPC URL for the blockchain
RPC_URL="https://forno.celo.org"

# Fetch the contracts from celocli and process the output
celocli network:contracts --node $RPC_URL | grep -E '0x[a-fA-F0-9]{40}' | while IFS=" " read -ra line
do
  # Extract contract name and implementation address from the output
  contract="${line[0]}"
  implementation_address="${line[2]}"

  # Ensure address is not empty or undefined
  if [ -z "$implementation_address" ] || [ "$implementation_address" == "NONE" ]; then
    echo "No implementation address available for $contract"
    echo "$contract,NONE,0 KB" >> $OUTPUT_FILE
    continue
  fi

  # Fetch bytecode using cast with the specified flag order
  bytecode=$(cast code $implementation_address --rpc-url $RPC_URL 2>/dev/null) # Redirect stderr to null to handle any errors gracefully

  # Check if bytecode was successfully fetched; handle cases where the contract might not have bytecode (e.g., non-contract addresses)
  if [[ -z "$bytecode" || "$bytecode" == "0x" ]]; then
    echo "No bytecode found for $contract at $implementation_address"
    echo "$contract,$implementation_address,0 KB" >> $OUTPUT_FILE
    continue
  fi

  # Calculate size in bytes (hex chars / 2)
  size_bytes=$(((${#bytecode} - 2) / 2))

  # Calculate size in kilobytes
  size_kb=$(echo "scale=3; $size_bytes / 1024" | bc)

  # Output result with contract name
  echo "Contract: $contract, Address: $implementation_address, Size: $size_kb KB" | tee -a $OUTPUT_FILE
done

Example output:

$ ./get_contract_bytecode_size.sh
Contract: Accounts, Address: 0x75Ec00c4B09574945F7d407797f8b61ca5AD9Cef, Size: 35.713 KB
Contract: Attestations, Address: 0x5729126891355E3C2626338151Dd50Cc0415E071, Size: 22.639 KB
Contract: BlockchainParameters, Address: 0x34FE128561A54F8d4185cd24d78634300B119725, Size: 10.877 KB
Contract: DoubleSigningSlasher, Address: 0x4Bb82B5862Beb483Fdb762EC4A6cB60953568A12, Size: 16.540 KB
Contract: DowntimeSlasher, Address: 0x9ebB6A46149a43C9D1B12EfdC068b969eCA7246F, Size: 20.266 KB
Contract: Election, Address: 0xCdE5039e3AcB3483aEebEBd59Cf6936056c455D4, Size: 43.262 KB
Contract: EpochRewards, Address: 0x563BA8Ed56bd32a964831aB6AfF1E53238177eDA, Size: 23.583 KB
Contract: Escrow, Address: 0xcC4E6caBe88EBb7FCCB40d862bf1C3a89f88e835, Size: 17.457 KB
Contract: FederatedAttestations, Address: 0x76A4daaC43912A443f098D413DED2Cb7A153EA85, Size: 13.846 KB
Contract: FeeCurrencyWhitelist, Address: 0xbC75342726648c4798543bac986c529f54220953, Size: 3.149 KB
Contract: FeeHandler, Address: 0x90eD9a893bC7be80F2D0E68650a8780A4E7373a5, Size: 20.698 KB
Contract: Freezer, Address: 0xa79cDb272799175A118A4Ce49ceCBF3eC86649e6, Size: 2.198 KB
Contract: GasPriceMinimum, Address: 0x52737f41EA463226af071933c823c2D550Bd6610, Size: 9.732 KB
Contract: GoldToken, Address: 0xcB8710e072aC4700eE7eD0C63B2f2102366a7a39, Size: 9.563 KB
Contract: Governance, Address: 0x19F78d207493Bf6f7E8D54900d01bb387F211b28, Size: 52.737 KB
Contract: LockedGold, Address: 0xbc518666F0827F4736220aC11c9a2cF37B1DE5aD, Size: 32.160 KB
Contract: MentoFeeHandlerSeller, Address: 0xBCF2374Fe8D582aB31840Ac08Fbc6bCD073d8c09, Size: 9.353 KB
Contract: OdisPayments, Address: 0x9Ea5E9b9B48a72325D59B3EBA147F42b1b14BF78, Size: 3.594 KB
Contract: Random, Address: 0xE43ea9C641a2af9959CaEEe54aDB089F65457028, Size: 12.037 KB
Contract: Registry, Address: 0x203fdf86A00999107Df531fa00b4bA81d674cb66, Size: 4.096 KB
Contract: Reserve, Address: 0xfD9651862Bc1965349E92073152112289393b57d, Size: 21.888 KB
Contract: SortedOracles, Address: 0x35a4f0C8C0B48769F036b79F9d428BeA286f6ab5, Size: 18.915 KB
Contract: StableToken, Address: 0x434563B0604BE100F04B7Ae485BcafE3c9D8850E, Size: 9.180 KB
Contract: StableTokenBRL, Address: 0x434563B0604BE100F04B7Ae485BcafE3c9D8850E, Size: 9.180 KB
Contract: StableTokenEUR, Address: 0x434563B0604BE100F04B7Ae485BcafE3c9D8850E, Size: 9.180 KB
Contract: UniswapFeeHandlerSeller, Address: 0xb851A5f1dbC743FdCe3009a3410df317cb33e24b, Size: 13.628 KB
Contract: Validators, Address: 0xe52EaC18fB3C1e1713e73d4A5b7dCb12a2f2C697, Size: 58.228 KB
arthurgousset commented 2 months ago

Next I'll compare this to the bytecode generated during the build process.

arthurgousset commented 2 months ago

I compared the contract sizes from the build artefacts, with the contract size from the deployed contracts. I pasted the results here for a quick comparison to a Google Sheet: Contract sizes.

Most core contracts yield about the same size (comparing the on-chain calculation and the build artefact calculation).

arthurgousset commented 2 months ago

Closing the loop here:

I calculated contract sizes in two ways (to double-check that there is no hidden optimization or other inconsistency):

  1. Using the bytecode of actually deployed smart contracts
  2. Using the build artifacts (deployedBytecode) of locally built smart contracts with Truffle

The scripts used for the calculations are in this PR:

The contract sizes are ordered and pasted here for ease of reference: Contract sizes

image

arthurgousset commented 1 month ago

Current max is

MaxCodeSize              = 65536           // Maximum bytecode to permit for a contract (2^16)

Source: protocol_params.go

arthurgousset commented 1 month ago

(for future reference) CEL2 size limit is set in this configuration file:

grep MaxCodeSize params/protocol_params.go
    MaxCodeSize     = 24576           // Maximum bytecode to permit for a contract
    MaxInitCodeSize = 2 * MaxCodeSize // Maximum initcode to permit in a creation transaction and create instructions

Source: celo-org/op-geth > params/protocol_params.go

PR to modify it: