oDAO Membership Interest - peteris

@peteris love your work.

I figured I’d take a stab at the last link, which is connecting from the audits to the now-verified contracts.

Consensys audit

Diff from reviewed commit 2023/11/23 to latest: Comparing f26996f..a08da96 · rocket-pool/rocketpool · GitHub
80 changed files with 2,299 additions and 582 deletions.

Sigma Prime Audit

Not available on GitHub - sigp/public-audits: Collection of public security reviews at this time, so link is to RP-hosted file. Would like to be able to get it from the 3rd party.

Diff from reviewed commit 2024/02/28 to latest: Comparing 7215562c..a08da96 · rocket-pool/rocketpool · GitHub
63 changed files with 1,346 additions and 273 deletions.

“Two follow up reviews were also conducted on Rocket Pool repository and were strictly limited to changes introduced in the commits e760442 and 7161d1c”. These commits are from 2024/05/07 and 2024/05/14 respectively.

Chainsafe audit

Reviewed at 2 commits initially, and then follow up after some additional updates by the dev team. The initial commits were from 2023/06/21 (6a9dbfd) and 2024/01/11 (60684a7) respectively. The additional verification commit was from 2024/04/22 (84ac198) and represents the latest audited point I see.

Diff from last reviewed commit: Comparing 84ac198..a08da96 · rocket-pool/rocketpool · GitHub
24 changed files with 365 additions and 126 deletions.

My own look at diffs

I’ll start from the latest audited point (84ac198) and comment on each commit after that. My looks are pretty cursory here, tbh. I am not deep into SC details, let alone this specific snapshot of the SCs. The main goal, cuz we don’t like trust in web3, is to see how much surface area there was for changes, especially unreviewed changes.

  • e760442 was reviewed by Sigma Prime; seems like it fixes (a) an issue with a malicious withdrawal address preventing distribution and (b) some mismatch between implementation and RPIP-31
    • pretty broad – definitely glad it got 3rd party review
  • e4fc9c8 claims to implement Sigma Prime’s recommendations in RPH-14; it looks like the changes do what it says on the tin
    • many changes, but they are all copies of the same change per RPH-14; quite narrow
  • 7161d1c was reviewed by Sigma Prime; says it corrects a vote power issue in phase 2 (the phase when delegates can be overridden). Frankly, I think I tracked in the end, but I’m less than 100.
    • voting details are important, so glad it got review; narrow scope
  • 5746fde increases the gas allowed to be used by a withdrawal address to receive a distribution from 2300 to 10000; straightforward
    • a single operative change and it is teeny
  • a08da963 fixes a comment
    • no operative changes

TL; DR – I’m satisfied that the audits+contract verification cover the necessary ground

5 Likes

Houston hotfix upgrade contract verification

Checking whether the source code published on GitHub matches what’s deployed on-chain.

This is similar to my verification of previous upgrades. Previous posts have more details how this works.

Payload

You can see the decoded payload of the proposal on Rocketscan:

Or you can verify it yourself:

new Interface(['function proposalUpgrade(string action, string contractName, string contractABI, address contractAddress)']).decodeFunctionData('proposalUpgrade', '0xdfc970ef...')

You should see:

  • Contract name: rocketUpgradeOneDotThreeDotOne
  • Contract address: 0xc2C81454427b1E53Fdf5d3B45561e3c18F90f9eD
  • Contract ABI: eJzNmMtu4jAU...

The contract ABI is compressed with zlib and base64-encoded. Here is a script to decode the ABI. You can also run it in your browser on replit.

Script to decode the payload and contract ABI
const ethers = require('ethers')
const pako = require('pako')

// proposal payload
// https://etherscan.io/tx/0x5d5737e5c5fe3aee8ce232c6462c4b2753be6c4833567d024dbbe7b21bc1000c
const payload = '0xdfc970ef000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000c2c81454427b1e53fdf5d3b45561e3c18f90f9ed000000000000000000000000000000000000000000000000000000000000000b616464436f6e7472616374000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e726f636b6574557067726164654f6e65446f745468726565446f744f6e65000000000000000000000000000000000000000000000000000000000000000003d0654a7a4e6d4d7475346a4155686c396c6c44554c4f334553707a7330624762526d564537367161714b73632b526c597a4e6e4b63586a5471753439546f454249434c5249395135382f622f7a4838655832332b52306f76473164484662667654676457732b764f79674f6769346b593779376a37646d5834413768725a79796277342b326b5751636f6b6d6b32642b3234623364626a4156776b4a642b32713348496574436c37764a6c48746d49504c78724653566371392b46707439494b39734c4b435451382f632b3173772f324176724257633831635937733172354e4436746d376a4c564b62635142635a4f3945667a664f4d3232426842514f62627075717076735659742f4744666a6258416e544c614e7a534e57386b37476c3032657431376d7873396379516b467a6b6177573636716a64444e2f75432b627661656b66756544512f47387a68574f37483656484255322b4533724b42615264642b4a79416273517745524a7a7a48596a746f47485a2b434e677a50376c4747533453496c49374f4b772f45756a616e36504877725032755545737753796d6b787048634f3771702f655a2b534c73642b4454344977536d4b63523450427233794247663257644a4549466f4f5a6c63375a54677563356d6741754634534b32477036584c732b6d7633395973544d327173437757575a7a524241597437694f596c756f77684e394c6c4a37334d6178717a6f70414742434d4342794a344177335661426d674341693575496a4a4b475a6b73516b4b784e2b43736f314f4f6546314e4e6d6636662f636d2b794a4d754141663445554741574156436134504b6b64624d6d576d646459427457495341565a54363436783646464a684e5259706c7776684a69586344566b6b464e69783353696c497a464879455a4c4154496c4a586c425a35714d6f6c307172685439337a4b43434f65736369372f6345656d2f30426e6d34312f704c6b5a676470522b2b30383553592f6d7547546148376f4457783873497a496a36666870736b4d526d426d70774468465a447970666f4a374d7662687872696c756f4373344c496f736344794e4962416a4d4245466854612b2f38596842457741372f374b52655744515352784e382f78772f45577753426d534170532f4a692b454b376733447432454e776130477947464f4230536b45675a6d517979497559734750653979387664766f756c2b56776635376e472f5738794b336c4c3837524b6e714c6c2f6265525043476e59583375666655564b4b557044444a2b46487350586f4861743932715239486930727a72747245483836464b6e507372762f63764c3263673d3d00000000000000000000000000000000'

// https://github.com/rocket-pool/rocketpool/blob/master/contracts/interface/dao/node/RocketDAONodeTrustedProposalsInterface.sol
const iface = new ethers.Interface([
  'function proposalInvite(string id, string url, address nodeAddress)',
  'function proposalLeave(address nodeAddress)',
  'function proposalKick(address nodeAddress, uint256 rplFine)',
  'function proposalSettingUint(string settingContractName, string settingPath, uint256 value)',
  'function proposalSettingBool(string settingContractName, string settingPath, bool value)',
  'function proposalUpgrade(string type, string name, string contractABI, address contractAddress)'
])

const result = iface.decodeFunctionData(iface.getFunction(payload.substring(0, 10)), payload).toObject()
console.log(result)

if (result.type == 'addContract' || result.type == 'upgradeContract') {
  const abi = JSON.parse(pako.inflate(Buffer.from(result.contractABI, 'base64'), { to: 'string' }))
  //console.log(abi) // json format
  console.log(new ethers.Interface(abi).format('full'))
}

Source code

Checking whether the source code published on Etherscan matches what’s on GitHub.

I ran the team’s verification tool.

Instructions
$ git clone https://github.com/rocket-pool/verify-1.3.1.git
$ cd verify-1.3.1/

$ git log --oneline | head -n1
30d5829 Update readme

$ cp .env.example .env
$ cat .env
ETH_RPC=http://xxx
NETWORK=mainnet
ETHERSCAN_API_KEY=xxx

# change .gitmodules to clone over HTTPS not SSH
$ cat .gitmodules
[submodule "rocketpool"]
	path = rocketpool
	url = https://github.com/rocket-pool/rocketpool.git
$ git submodule sync

Here is the output:

$ docker run --rm -it -v $(pwd):/app -w /app node:20 sh -c 'npm install && ./verify.sh'
...
Cloning into '/app/rocketpool'...
Submodule path 'rocketpool': checked out '8d4d5c0f1b810f97ca42cd1ceece0e7c81eb81ee'
...
added 998 packages, and audited 999 packages in 19s
...
✔ Verified contract at 0xc2C81454427b1E53Fdf5d3B45561e3c18F90f9eD matches RocketUpgradeOneDotThreeDotOne
✔ Verified contract at 0x1e94e6131Ba5B4F193d2A1067517136C52ddF102 matches RocketDAOProposal
✔ Verified contract at 0x2D627A50Dc1C4EDa73E42858E8460b0eCF300b25 matches RocketDAOProtocolProposal
✔ Verified contract at 0xd1f7e573cdC64FC0B201ca37aB50bC7Dd880040A matches RocketDAOProtocolVerifier
✔ Verified contract at 0x59cd103DF1BE2EBd80D45c54a3cDE8d4F812C034 matches RocketDAOProtocolSettingsProposals
✔ Verified contract at 0x364F989A3C9a1F66cB51b9043680974eA08C0d18 matches RocketDAOProtocolAuction
✔ Verified contract at 0xF82991Bd8976c243eB3b7CDDc52AB0Fc8dc1246C matches RocketMinipoolManager
✔ Verified contract at 0xF18Dc176C10Ff6D8b5A17974126D43301F8EEB95 matches RocketNodeStaking
✔ Verified contract at 0x03d30466d199Ef540823fe2a22CAE2E3b9343bb0 matches RocketMinipoolDelegate
✔ Verified contract at 0x672335B91b4f2096D897cA1B12Ef4ec9346A5ff4 matches RocketNodeDeposit
✔ Verified contract at 0x77cF0f32BDd06242465eb3318a81196194a13daA matches RocketNetworkVoting
ETH matched corrections:
 1: 0x9796dAd6a55c9501F83B0Dc41676bdC6d001dd32 = 16000000000000000000
 2: 0x70D06394f33D56B6310778eC4E61033585038997 = 16000000000000000000
 3: 0x4efc3E587A4c3Ae0899a0F6e20a78393FC9E39C8 = 16000000000000000000
✔ Verification successful

8d4d5c0 is the commit with the v1.3.1 tag.

Bytecode

This verifies whether the bytecode of the contracts on-chain matches the bytecode produced by compiling the source code from GitHub.

Script

Get the source code and compile the contracts:

git clone https://github.com/rocket-pool/rocketpool.git
cd rocketpool
git checkout 8d4d5c0
npm install
npm run compile

Install dependencies for the verification script:

npm install --no-save ethers@5 glob@10 ganache-cli

Fork mainnet and impersonate Rocket Pool’s deployer address:

npx ganache-cli --fork https://cloudflare-eth.com --unlock 0x27e80db1f5a975f4c43c5ec163114e796cdb603d --gasPrice 1

Create a file called verify.js:

const fs = require('node:fs/promises')
const { glob } = require('glob') // npm install glob@10
const { Contract, ContractFactory } = require('ethers') // npm install ethers@5
const { StaticJsonRpcProvider } = require('@ethersproject/providers')

async function main() {
  const providerUrl = 'http://127.0.0.1:8545'

  const deployer = '0x27e80db1f5a975f4c43c5ec163114e796cdb603d'
  const storageAddress = '0x1d8f8f00cfa6758d7bE78336684788Fb0ee0Fa46'
  const upgradeContractAddress = '0xc2C81454427b1E53Fdf5d3B45561e3c18F90f9eD'

  const upgradeContractABI = [
    'function newRocketDAOProposal() view returns (address)',
    'function newRocketDAOProtocolProposal() view returns (address)',
    'function newRocketDAOProtocolSettingsAuction() view returns (address)',
    'function newRocketDAOProtocolSettingsProposals() view returns (address)',
    'function newRocketDAOProtocolVerifier() view returns (address)',
    'function newRocketMinipoolDelegate() view returns (address)',
    'function newRocketMinipoolManager() view returns (address)',
    'function newRocketNetworkVoting() view returns (address)',
    'function newRocketNodeDeposit() view returns (address)',
    'function newRocketNodeStaking() view returns (address)'
  ]

  const provider = new StaticJsonRpcProvider(providerUrl)

  const upgradeContract = new Contract(upgradeContractAddress, upgradeContractABI, provider)

  const contracts = [
    ['rocketUpgradeOneDotThreeDotOne', upgradeContractAddress],
    ...(await Promise.all(
      Object.values(upgradeContract.interface.functions).map(async f => [
        f.name.replace('new', ''),
        await upgradeContract[f.name]()
      ])
    ))
  ]

  for (const [contractName, address] of contracts) {
    const blockchainCode = await provider.getCode(address)

    const artifactName = contractName[0].toUpperCase() + contractName.substring(1) // rocketContract => RocketContract
    const artifactFilename = (await glob(`artifacts/**/${artifactName}.sol/${artifactName}.json`))[0]
    const artifact = JSON.parse(await fs.readFile(artifactFilename, 'utf-8'))

    const factory = ContractFactory.fromSolidity(artifact, provider.getSigner(deployer))
    const deployment = artifactName.includes('Delegate') ? await factory.deploy() : await factory.deploy(storageAddress)
    await deployment.deployTransaction.wait()
    const deployedArtifactCode = await provider.getCode(deployment.address)

    const match = blockchainCode === deployedArtifactCode
    console.log(match ? '✅' : '❌', artifactName, address)
  }
}

main()

Run the verification script:

node verify.js

Script output for 8d4d5c0f1b810f97ca42cd1ceece0e7c81eb81ee:

✅ RocketUpgradeOneDotThreeDotOne 0xc2C81454427b1E53Fdf5d3B45561e3c18F90f9eD
✅ RocketDAOProposal 0x1e94e6131Ba5B4F193d2A1067517136C52ddF102
✅ RocketDAOProtocolProposal 0x2D627A50Dc1C4EDa73E42858E8460b0eCF300b25
✅ RocketDAOProtocolSettingsAuction 0x364F989A3C9a1F66cB51b9043680974eA08C0d18
✅ RocketDAOProtocolSettingsProposals 0x59cd103DF1BE2EBd80D45c54a3cDE8d4F812C034
✅ RocketDAOProtocolVerifier 0xd1f7e573cdC64FC0B201ca37aB50bC7Dd880040A
✅ RocketMinipoolDelegate 0x03d30466d199Ef540823fe2a22CAE2E3b9343bb0
✅ RocketMinipoolManager 0xF82991Bd8976c243eB3b7CDDc52AB0Fc8dc1246C
✅ RocketNetworkVoting 0x77cF0f32BDd06242465eb3318a81196194a13daA
✅ RocketNodeDeposit 0x672335B91b4f2096D897cA1B12Ef4ec9346A5ff4
✅ RocketNodeStaking 0xF18Dc176C10Ff6D8b5A17974126D43301F8EEB95

Everything matches.

Settings

The upgrade contract was deployed with 0x1d8f8f00cfa6758d7bE78336684788Fb0ee0Fa46 as the RocketStorage address which is correct.

Upgraded contract addresses were set once in a separate transaction and I’ve checked them in this post.

There were 3 ETH matched corrections added but I have not checked if they are correct or not.

Finally the upgrade contract was locked which means contract addresses cannot be changed and no more corrections can be added.

The upgrade can be executed by the Protocol DAO guardian (an EOA controlled by the team) at their convenience which is scheduled for October 28 at 00:00 UTC.

Conclusion

I have verified myself that the contracts whose source code is published on GitHub are the same ones that are deployed on-chain and will be used for the Houston hotfix upgrade.

4 Likes

Saturn 1 upgrade contract verification

Checking whether the source code published on GitHub matches what’s deployed on-chain.

This is similar to my verification of previous upgrades. Previous posts have more details how this works.

Payload

You can verify it yourself:

new Interface(['function proposalUpgrade(string action, string contractName, string contractABI, address contractAddress)']).decodeFunctionData('proposalUpgrade', '0xdfc970ef...')

You should see:

  • Contract name: rocketUpgradeOneDotFour
  • Contract address: 0x5b3B5C76391662e56d0ff72F31B89C409316c8Ba
  • Contract ABI: eJzVVMFOhDAQ...

The contract ABI is compressed with zlib and base64-encoded. Here is a script to decode the ABI.

Script to decode the payload and contract ABI
const ethers = require('ethers') // npm install ethers@6
const pako = require('pako') // npm install pako

// proposal payload
// https://etherscan.io/tx/0xd8a298c4e4b95fc8873b6ac1c3ef02c23c70c88344c02abff0b222586474f242
const payload = '0xdfc970ef000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000005b3b5c76391662e56d0ff72f31b89c409316c8ba000000000000000000000000000000000000000000000000000000000000000b616464436f6e74726163740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017726f636b6574557067726164654f6e65446f74466f757200000000000000000000000000000000000000000000000000000000000000000000000000000001b8654a7a56564d464f684441512f5258544d78644e56485a76635050675a66564769436c6c6c68445a6c72545456574c3233323046685955436d6f4447473351656239347262795a36497a6b764e5371796a65776a67755330654b784b494676434245644a47563773424873476645416861515a334672536e444968484f443159344a5073416f49306c6143554b57504e51357544552b77526852546858694e4e38694c48796c53353443577461464a412b345870724642715a676a4a795a7653714d33373166564e7136556c2b537a5a746b3352644657425151694e5934536d6238347a463139546362733435764453517665614d387746583178382b472f4631786d417565756e582b455a73453748364463397a50794656543230637541566d455934302f4c74435a736a7a7742333771482b652b4f46465a624f42436b526f6e414a2b5468665473564d46465a534d58627a3065567433466e4c5a304f485132544e36646767665a356d6265494130356b4f425267736d6356786a373754592b6a773645393633505139686b4f506d373748634a31354f344a55746a795a4a7275542f4c466c35663877542f4537573836324e673d3d0000000000000000'

// https://github.com/rocket-pool/rocketpool/blob/master/contracts/interface/dao/node/RocketDAONodeTrustedProposalsInterface.sol
const iface = new ethers.Interface([
  'function proposalInvite(string id, string url, address nodeAddress)',
  'function proposalLeave(address nodeAddress)',
  'function proposalKick(address nodeAddress, uint256 rplFine)',
  'function proposalSettingUint(string settingContractName, string settingPath, uint256 value)',
  'function proposalSettingBool(string settingContractName, string settingPath, bool value)',
  'function proposalUpgrade(string type, string name, string contractABI, address contractAddress)'
])

const result = iface.decodeFunctionData(iface.getFunction(payload.substring(0, 10)), payload).toObject()
console.log(result)

if (result.type == 'addContract' || result.type == 'upgradeContract') {
  const abi = JSON.parse(pako.inflate(Buffer.from(result.contractABI, 'base64'), { to: 'string' }))
  //console.log(abi) // json format
  console.log(new ethers.Interface(abi).format('full'))
}

Source code

Checking whether the source code published on Etherscan matches what’s on GitHub.

I ran the team’s verification tool.

Instructions
$ git clone https://github.com/rocket-pool/verify-1.4.git
$ cd verify-1.4/

$ git log --oneline | head -n1
79df9f0 Add mainnet address

$ cp .env.example .env
$ cat .env
NETWORK=mainnet
ETHERSCAN_API_KEY=xxx
ETH_RPC=http://xxx
ROCKET_STORAGE=0x1d8f8f00cfa6758d7bE78336684788Fb0ee0Fa46

# change .gitmodules to clone over HTTPS not SSH
$ cat .gitmodules
[submodule "rocketpool"]
path = rocketpool
url = https://github.com/rocket-pool/rocketpool.git
$ git submodule sync

Here is the output:

$ docker run --rm -it -v $(pwd):/app -w /app node:20 sh -c 'npm install && ./verify.sh'
...
Cloning into '/app/rocketpool'...
Submodule path 'rocketpool': checked out '5049193cb94c6e153c3ea5784fc670b95b828bf2'
...
✔ Verified contract at 0x5b3B5C76391662e56d0ff72F31B89C409316c8Ba matches RocketUpgradeOneDotFour
✔ Verified RocketStorage matches 0x1d8f8f00cfa6758d7bE78336684788Fb0ee0Fa46
✔ Verified contract at 0xca3DD4bee7C174903dBF66c3897c27E9ADaAEBdD matches RocketMegapoolDelegate
✔ Verified contract at 0xD5bffeaa9f373B9C367132772FAA0b88e3F0E38b matches RocketMegapoolFactory
✔ Verified contract at 0x1B389D76a04d01026c5f5B0a125D4CCF26F9cd51 matches RocketMegapoolProxy
✔ Verified contract at 0xf2CCd522Ba5fFEda28fe0389963845D61F342034 matches RocketMegapoolManager
✔ Verified contract at 0xcf2d76A7499d3acB5A22ce83c027651e8d76e250 matches RocketNodeManager
✔ Verified contract at 0x6B13698c306a297Fee1383cdC2c65d63781D2D47 matches RocketNodeDeposit
✔ Verified contract at 0xedFc7DCaE43fF954577a2875a9D805874490eE3E matches RocketNodeStaking
✔ Verified contract at 0xCE15294273CFb9D9b628F4D61636623decDF4fdC matches RocketDepositPool
✔ Verified contract at 0x52590E8aaC140E2020f8F51695719922ebcCb6D6 matches LinkedListStorage
✔ Verified contract at 0xCaC25e88276A333cF9d4196d112D93af67ef809A matches RocketDAOProtocol
✔ Verified contract at 0xcf7F6E23cD8189B7F56b14F66e11241C8ac0F03b matches RocketDAOProtocolPropsals
✔ Verified contract at 0xb02B883303e658Ddcd58D3871Dc4Ca0C91f0fc9D matches RocketDAOProtocolSettingsNode
✔ Verified contract at 0x227BE8dD01DF8ad9BED0178e4F8cEC2996C5c365 matches RocketDAOProtocolSettingsDeposit
✔ Verified contract at 0x67Fd03a5095197D1aD1F932BC55E022C420b1153 matches RocketDAOProtocolSettingsNetwork
✔ Verified contract at 0xC9D771AaF504F33bB3C8a7E67eA9f1881F837cFf matches RocketDAOProtocolSettingsSecurity
✔ Verified contract at 0x40628FAAc22383327b9f7bBc86CD1857050A2dCe matches RocketDAOProtocolSettingsMegapool
✔ Verified contract at 0xaeF94C3650AA13d7A2456477fc374a16b94B9152 matches RocketDAOProtocolSettingsMinipool
✔ Verified contract at 0x950BaF0358164339114914169BF16754789B5Dc4 matches RocketDAOSecurtityUpgrade
✔ Verified contract at 0x334B9B1a6F9d7531efb13746482ff40f1c2a0c4e matches RocketDAOSecurityProposals
✔ Verified contract at 0x9290AA076a2F1418a4E414E3D83AE03cA8E1ad10 matches RocketDAONodeTrustedUpgrade
✔ Verified contract at 0x9D9708dA8E0200Dd8Dd9ad09e0AAf184Ad260842 matches RocketNetworkRevenues
✔ Verified contract at 0x1D9F14C6Bfd8358b589964baD8665AdD248E9473 matches RocketNetworkBalances
✔ Verified contract at 0xe37F2d9dFb7397caF671DF5190a5dFB601028f17 matches RocketNetworkSnapshots
✔ Verified contract at 0xeD0493DE30e82bE7C16C8925C7204CE9D1136B3a matches RocketNetworkPenalties
✔ Verified contract at 0xCba5951fc706Fc783b7C142DaE8576Ebe29c41FD matches RocketRewardsPool
✔ Verified contract at 0xE9a114c50f26001443B91079Ab5573a90D2D8469 matches BeaconStateVerifier
✔ Verified contract at 0x35A85d4c115801395e6E3abAa784Fb05826f129D matches RocketNodeDistributorDelegate
✔ Verified contract at 0xfB2F2Ab63DCf412ced6cdE5f4f809215ed0c81aa matches RocketClaimDAO
✔ Verified contract at 0xDe8Ab526b19FCA2D5a57c4A78b698041717BE591 matches RocketMinipoolBondReducer
✔ Verified contract at 0xe54B8C641fd96dE5D6747f47C19964c6b824D62C matches RocketMinipoolManager
✔ Verified contract at 0x994A9C49230FEC0c127B8F42D6c5288F02610AeD matches RocketNetworkVoting
✔ Verified contract at 0xE4E2612EE8d7fdc8518Faea85770A3b9c886E2f5 matches RocketMerkleDistributorMainnet
✔ Verified contract at 0xa2afC3C2d8ea4eBdbE925cADe17c29517630e6aB matches RocketMegapoolPenalties
✔ Verified contract at 0x569F5b3024054AB4049A50df223a747AFE18a891 matches RocketNetworkSnapshotsTime
✔ Verified contract at 0xf6ad771dfB1cd10c66F688E251b5E5c21cbfDF81 matches RocketDAOProtocolSettingsProposal
✔ Verification successful

5049193 is actually not the last commit for the v1.4 branch, fb7d9c4 is.

$ cd rocketpool && git checkout fb7d9c428dc3dddc3fbd3e634e3cb365655df89e && cd .. && git add rocketpool && rm -rf rocketpool
HEAD is now at fb7d9c42 Insert correct mainnet genesis block time

$ docker run --rm -it -v $(pwd):/app -w /app node:20 sh -c 'npm install && ./verify.sh'
...
Submodule path 'rocketpool': checked out 'fb7d9c428dc3dddc3fbd3e634e3cb365655df89e'
...
✔ Verified contract at 0x5b3B5C76391662e56d0ff72F31B89C409316c8Ba matches RocketUpgradeOneDotFour
✔ Verified RocketStorage matches 0x1d8f8f00cfa6758d7bE78336684788Fb0ee0Fa46
✔ Verified contract at 0xca3DD4bee7C174903dBF66c3897c27E9ADaAEBdD matches RocketMegapoolDelegate
✔ Verified contract at 0xD5bffeaa9f373B9C367132772FAA0b88e3F0E38b matches RocketMegapoolFactory
✔ Verified contract at 0x1B389D76a04d01026c5f5B0a125D4CCF26F9cd51 matches RocketMegapoolProxy
✔ Verified contract at 0xf2CCd522Ba5fFEda28fe0389963845D61F342034 matches RocketMegapoolManager
✔ Verified contract at 0xcf2d76A7499d3acB5A22ce83c027651e8d76e250 matches RocketNodeManager
✔ Verified contract at 0x6B13698c306a297Fee1383cdC2c65d63781D2D47 matches RocketNodeDeposit
✔ Verified contract at 0xedFc7DCaE43fF954577a2875a9D805874490eE3E matches RocketNodeStaking
✔ Verified contract at 0xCE15294273CFb9D9b628F4D61636623decDF4fdC matches RocketDepositPool
✔ Verified contract at 0x52590E8aaC140E2020f8F51695719922ebcCb6D6 matches LinkedListStorage
✔ Verified contract at 0xCaC25e88276A333cF9d4196d112D93af67ef809A matches RocketDAOProtocol
✔ Verified contract at 0xcf7F6E23cD8189B7F56b14F66e11241C8ac0F03b matches RocketDAOProtocolPropsals
✔ Verified contract at 0xb02B883303e658Ddcd58D3871Dc4Ca0C91f0fc9D matches RocketDAOProtocolSettingsNode
✔ Verified contract at 0x227BE8dD01DF8ad9BED0178e4F8cEC2996C5c365 matches RocketDAOProtocolSettingsDeposit
✔ Verified contract at 0x67Fd03a5095197D1aD1F932BC55E022C420b1153 matches RocketDAOProtocolSettingsNetwork
✔ Verified contract at 0xC9D771AaF504F33bB3C8a7E67eA9f1881F837cFf matches RocketDAOProtocolSettingsSecurity
✔ Verified contract at 0x40628FAAc22383327b9f7bBc86CD1857050A2dCe matches RocketDAOProtocolSettingsMegapool
✔ Verified contract at 0xaeF94C3650AA13d7A2456477fc374a16b94B9152 matches RocketDAOProtocolSettingsMinipool
✔ Verified contract at 0x950BaF0358164339114914169BF16754789B5Dc4 matches RocketDAOSecurtityUpgrade
✔ Verified contract at 0x334B9B1a6F9d7531efb13746482ff40f1c2a0c4e matches RocketDAOSecurityProposals
✔ Verified contract at 0x9290AA076a2F1418a4E414E3D83AE03cA8E1ad10 matches RocketDAONodeTrustedUpgrade
✔ Verified contract at 0x9D9708dA8E0200Dd8Dd9ad09e0AAf184Ad260842 matches RocketNetworkRevenues
✔ Verified contract at 0x1D9F14C6Bfd8358b589964baD8665AdD248E9473 matches RocketNetworkBalances
✔ Verified contract at 0xe37F2d9dFb7397caF671DF5190a5dFB601028f17 matches RocketNetworkSnapshots
✔ Verified contract at 0xeD0493DE30e82bE7C16C8925C7204CE9D1136B3a matches RocketNetworkPenalties
✔ Verified contract at 0xCba5951fc706Fc783b7C142DaE8576Ebe29c41FD matches RocketRewardsPool
✔ Verified contract at 0xE9a114c50f26001443B91079Ab5573a90D2D8469 matches BeaconStateVerifier
✔ Verified contract at 0x35A85d4c115801395e6E3abAa784Fb05826f129D matches RocketNodeDistributorDelegate
✔ Verified contract at 0xfB2F2Ab63DCf412ced6cdE5f4f809215ed0c81aa matches RocketClaimDAO
✔ Verified contract at 0xDe8Ab526b19FCA2D5a57c4A78b698041717BE591 matches RocketMinipoolBondReducer
✔ Verified contract at 0xe54B8C641fd96dE5D6747f47C19964c6b824D62C matches RocketMinipoolManager
✔ Verified contract at 0x994A9C49230FEC0c127B8F42D6c5288F02610AeD matches RocketNetworkVoting
✔ Verified contract at 0xE4E2612EE8d7fdc8518Faea85770A3b9c886E2f5 matches RocketMerkleDistributorMainnet
✔ Verified contract at 0xa2afC3C2d8ea4eBdbE925cADe17c29517630e6aB matches RocketMegapoolPenalties
✔ Verified contract at 0x569F5b3024054AB4049A50df223a747AFE18a891 matches RocketNetworkSnapshotsTime
✔ Verified contract at 0xf6ad771dfB1cd10c66F688E251b5E5c21cbfDF81 matches RocketDAOProtocolSettingsProposal
✔ Verification successful

Bytecode

This verifies whether the bytecode of the contracts on-chain matches the bytecode produced by compiling the source code from GitHub.

Script

Get the source code and compile the contracts:

git clone https://github.com/rocket-pool/rocketpool.git
cd rocketpool
git checkout fb7d9c4
npm install
npx hardhat compile --config hardhat-deploy.config.js

Install dependencies for the verification script:

npm install --no-save ganache-cli

Fork mainnet and impersonate Rocket Pool’s deployer address:

npx ganache-cli --fork https://ethereum-rpc.publicnode.com --unlock 0x27e80db1f5a975f4c43c5ec163114e796cdb603d --gasPrice 1

Create a file called verify.js:

const fs = require("node:fs/promises")
const { Contract, ContractFactory, JsonRpcProvider, JsonRpcSigner } = require("ethers")

async function main() {
  const providerUrl = "http://127.0.0.1:8545"

  const storageAddress = "0x1d8f8f00cfa6758d7bE78336684788Fb0ee0Fa46"
  const upgradeContractAddress = "0x5b3B5C76391662e56d0ff72F31B89C409316c8Ba"
  const deployer = "0x27e80db1f5a975f4c43c5ec163114e796cdb603d"

  const provider = new JsonRpcProvider(providerUrl)
  const signer = new JsonRpcSigner(provider, deployer)

  const ugpradeContractABI = [
    "function lockedA() view returns (bool)",
    "function lockedB() view returns (bool)",
    "function addressesA(uint256) view returns (address)",
    "function addressesB(uint256) view returns (address)"
  ]

  const upgradeContract = new Contract(upgradeContractAddress, ugpradeContractABI, provider)

  const contracts = [
    ["rocketUpgradeOneDotFour", upgradeContractAddress],
    ["rocketMegapoolDelegate", await upgradeContract.addressesA(0)],
    ["rocketMegapoolFactory", await upgradeContract.addressesA(1)],
    ["rocketMegapoolProxy", await upgradeContract.addressesA(2)],
    ["rocketMegapoolManager", await upgradeContract.addressesA(3)],
    ["linkedListStorage", await upgradeContract.addressesA(8)],
    ["rocketDAOProtocolSettingsMegapool", await upgradeContract.addressesA(15)],
    ["rocketDAOSecurityUpgrade", await upgradeContract.addressesB(0)],
    ["rocketNetworkRevenues", await upgradeContract.addressesB(3)],
    ["rocketMegapoolPenalties", await upgradeContract.addressesB(15)],
    ["rocketNetworkSnapshotsTime", await upgradeContract.addressesB(16)],
    ["rocketNodeManager", await upgradeContract.addressesA(4)],
    ["rocketNodeDeposit", await upgradeContract.addressesA(5)],
    ["rocketNodeStaking", await upgradeContract.addressesA(6)],
    ["rocketDepositPool", await upgradeContract.addressesA(7)],
    ["rocketDAOProtocol", await upgradeContract.addressesA(9)],
    ["rocketDAOProtocolProposals", await upgradeContract.addressesA(10)],
    ["rocketDAOProtocolSettingsNode", await upgradeContract.addressesA(11)],
    ["rocketDAOProtocolSettingsDeposit", await upgradeContract.addressesA(12)],
    ["rocketDAOProtocolSettingsNetwork", await upgradeContract.addressesA(13)],
    ["rocketDAOProtocolSettingsSecurity", await upgradeContract.addressesA(14)],
    ["rocketDAOProtocolSettingsMinipool", await upgradeContract.addressesA(16)],
    ["rocketDAOSecurityProposals", await upgradeContract.addressesB(1)],
    ["rocketDAONodeTrustedUpgrade", await upgradeContract.addressesB(2)],
    ["rocketNetworkBalances", await upgradeContract.addressesB(4)],
    ["rocketNetworkSnapshots", await upgradeContract.addressesB(5)],
    ["rocketNetworkPenalties", await upgradeContract.addressesB(6)],
    ["rocketRewardsPool", await upgradeContract.addressesB(7)],
    ["rocketNodeDistributorDelegate", await upgradeContract.addressesB(9), null],
    ["rocketClaimDAO", await upgradeContract.addressesB(10)],
    ["rocketMinipoolBondReducer", await upgradeContract.addressesB(11)],
    ["rocketMinipoolManager", await upgradeContract.addressesB(12)],
    ["rocketNetworkVoting", await upgradeContract.addressesB(13)],
    ["rocketMerkleDistributorMainnet", await upgradeContract.addressesB(14)],
    ["rocketDAOProtocolSettingsProposals", await upgradeContract.addressesB(17)],
    // https://etherscan.io/address/0xE9a114c50f26001443B91079Ab5573a90D2D8469#code
    [
      "beaconStateVerifier",
      await upgradeContract.addressesB(8),
      [
        8192,
        [2375680, 4636672, 6209536, 8626176, 11649024],
        "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02",
        1606824023,
        "0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95"
      ]
    ]
  ]

  const artifactFiles = await fs.readdir("artifacts", { recursive: true })

  for (let [contractName, address, args] of contracts) {
    if (args === null) args = []
    else if (args === undefined) args = [storageAddress]
    else args = [storageAddress, ...args]

    const blockchainCode = await provider.getCode(address)

    const artifactName = contractName[0].toUpperCase() + contractName.substring(1) // rocketContract => RocketContract
    const artifactFilename = artifactFiles.find(f => f.endsWith(`${artifactName}.sol/${artifactName}.json`))
    const artifact = JSON.parse(await fs.readFile(`artifacts/${artifactFilename}`, "utf-8"))

    const factory = new ContractFactory(artifact.abi, artifact.bytecode, signer)
    const deployment = await factory.deploy(...args)
    await deployment.waitForDeployment()
    const deploymentAddress = await deployment.getAddress()
    let deployedArtifactCode = await provider.getCode(deploymentAddress)

    if (artifactName === "RocketMegapoolProxy") {
      // Claude Opus 4.6:
      //   The two bytecodes are identical except for a single immutable address that appears 6 times throughout the code.
      //   This is the RocketMegapoolProxy's own contract address, embedded as an immutable address(this) reference used for proxy identity checks (the address(this) == <expected> guard pattern that prevents calling implementation logic directly instead of through the proxy).
      //   It appears at 6 locations in the bytecode, which corresponds to the 6 functions/modifiers that use require(address(this) != __self) or similar delegate-call guards.
      deployedArtifactCode = deployedArtifactCode.replaceAll(
        deploymentAddress.toLowerCase().substring(2),
        address.toLowerCase().substring(2)
      )
    }

    const match = blockchainCode === deployedArtifactCode
    console.log(match ? "✅" : "❌", artifactName, address)
  }

  console.log((await upgradeContract.lockedA()) ? "✅" : "❌", "setA locked")
  console.log((await upgradeContract.lockedB()) ? "✅" : "❌", "setB locked")
}

main()

Run the verification script:

node verify.js

Script output for fb7d9c428dc3dddc3fbd3e634e3cb365655df89e

✅ RocketUpgradeOneDotFour 0x5b3B5C76391662e56d0ff72F31B89C409316c8Ba
✅ RocketMegapoolDelegate 0xca3DD4bee7C174903dBF66c3897c27E9ADaAEBdD
✅ RocketMegapoolFactory 0xD5bffeaa9f373B9C367132772FAA0b88e3F0E38b
✅ RocketMegapoolProxy 0x1B389D76a04d01026c5f5B0a125D4CCF26F9cd51
✅ RocketMegapoolManager 0xf2CCd522Ba5fFEda28fe0389963845D61F342034
✅ LinkedListStorage 0x52590E8aaC140E2020f8F51695719922ebcCb6D6
✅ RocketDAOProtocolSettingsMegapool 0x40628FAAc22383327b9f7bBc86CD1857050A2dCe
✅ RocketDAOSecurityUpgrade 0x950BaF0358164339114914169BF16754789B5Dc4
✅ RocketNetworkRevenues 0x9D9708dA8E0200Dd8Dd9ad09e0AAf184Ad260842
✅ RocketMegapoolPenalties 0xa2afC3C2d8ea4eBdbE925cADe17c29517630e6aB
✅ RocketNetworkSnapshotsTime 0x569F5b3024054AB4049A50df223a747AFE18a891
✅ RocketNodeManager 0xcf2d76A7499d3acB5A22ce83c027651e8d76e250
✅ RocketNodeDeposit 0x6B13698c306a297Fee1383cdC2c65d63781D2D47
✅ RocketNodeStaking 0xedFc7DCaE43fF954577a2875a9D805874490eE3E
✅ RocketDepositPool 0xCE15294273CFb9D9b628F4D61636623decDF4fdC
✅ RocketDAOProtocol 0xCaC25e88276A333cF9d4196d112D93af67ef809A
✅ RocketDAOProtocolProposals 0xcf7F6E23cD8189B7F56b14F66e11241C8ac0F03b
✅ RocketDAOProtocolSettingsNode 0xb02B883303e658Ddcd58D3871Dc4Ca0C91f0fc9D
✅ RocketDAOProtocolSettingsDeposit 0x227BE8dD01DF8ad9BED0178e4F8cEC2996C5c365
✅ RocketDAOProtocolSettingsNetwork 0x67Fd03a5095197D1aD1F932BC55E022C420b1153
✅ RocketDAOProtocolSettingsSecurity 0xC9D771AaF504F33bB3C8a7E67eA9f1881F837cFf
✅ RocketDAOProtocolSettingsMinipool 0xaeF94C3650AA13d7A2456477fc374a16b94B9152
✅ RocketDAOSecurityProposals 0x334B9B1a6F9d7531efb13746482ff40f1c2a0c4e
✅ RocketDAONodeTrustedUpgrade 0x9290AA076a2F1418a4E414E3D83AE03cA8E1ad10
✅ RocketNetworkBalances 0x1D9F14C6Bfd8358b589964baD8665AdD248E9473
✅ RocketNetworkSnapshots 0xe37F2d9dFb7397caF671DF5190a5dFB601028f17
✅ RocketNetworkPenalties 0xeD0493DE30e82bE7C16C8925C7204CE9D1136B3a
✅ RocketRewardsPool 0xCba5951fc706Fc783b7C142DaE8576Ebe29c41FD
✅ RocketNodeDistributorDelegate 0x35A85d4c115801395e6E3abAa784Fb05826f129D
✅ RocketClaimDAO 0xfB2F2Ab63DCf412ced6cdE5f4f809215ed0c81aa
✅ RocketMinipoolBondReducer 0xDe8Ab526b19FCA2D5a57c4A78b698041717BE591
✅ RocketMinipoolManager 0xe54B8C641fd96dE5D6747f47C19964c6b824D62C
✅ RocketNetworkVoting 0x994A9C49230FEC0c127B8F42D6c5288F02610AeD
✅ RocketMerkleDistributorMainnet 0xE4E2612EE8d7fdc8518Faea85770A3b9c886E2f5
✅ RocketDAOProtocolSettingsProposals 0xf6ad771dfB1cd10c66F688E251b5E5c21cbfDF81
✅ BeaconStateVerifier 0xE9a114c50f26001443B91079Ab5573a90D2D8469
✅ setA locked
✅ setB locked

Everything matches.

Settings

The upgrade contract sets various protocol parameters but I did not check them.

The upgrade contract has been locked which means contract addresses cannot be changed.

Conclusion

I have verified myself that the contracts whose source code is published on GitHub are the same ones that are deployed on-chain and will be used for the Saturn 1 upgrade.

2 Likes