From bf2660c106b8c053bb308e9c97cc4a99571618cc Mon Sep 17 00:00:00 2001 From: engn33r Date: Tue, 24 Feb 2026 00:00:00 +0000 Subject: [PATCH 1/4] Add multisig members check --- scripts/fetchedAddressData.json | 37 ++++++- scripts/runAddressChecks.ts | 32 +++++- src/ethereum/ABIs/gnosisSafeABI.ts | 9 ++ src/ethereum/ABIs/index.ts | 1 + src/ethereum/multisigCalls.ts | 18 ++++ src/ethereum/multisigChecks.ts | 163 +++++++++++++++++++++++++++++ src/ethereum/types.ts | 20 ++++ 7 files changed, 275 insertions(+), 5 deletions(-) create mode 100644 src/ethereum/ABIs/gnosisSafeABI.ts create mode 100644 src/ethereum/multisigCalls.ts create mode 100644 src/ethereum/multisigChecks.ts diff --git a/scripts/fetchedAddressData.json b/scripts/fetchedAddressData.json index 203de6e16..95239fb78 100644 --- a/scripts/fetchedAddressData.json +++ b/scripts/fetchedAddressData.json @@ -1,5 +1,5 @@ { - "timeLastChecked": 1770347214, + "timeLastChecked": 1771939655, "addressesData": { "v3ContractAddresses": { "topLevel": { @@ -71,11 +71,38 @@ "yGauge DAI-2 yVault": "0x38E3d865e34f7367a69f096C80A4fc329DB38BF4", "yGauge WETH-2 yVault": "0x8E2485942B399EA41f3C910c1Bb8567128f79859", "yGauge crvUSD-2 yVault": "0x71c3223D6f836f84cAA7ab5a68AAb6ECe21A9f3b" + }, + "yearnMultisigMembers": { + "multisigAddress": "0xFEB4acf3df3cDEA7399794D0869ef76A6EfAff52", + "docsMemberAddresses": [ + "0x6F2A8Ee9452ba7d336b3fba03caC27f7818AeAD6", + "0xeA6c0837fef621E77329f85820F503cA09f2B3a9", + "0x70aF5a3368606c6557D2B3ce2EEC8796B914EAa3", + "0xf5D3dbda5F41A0E26D71B948e29522398e71cFaE", + "0x5Db9926c93085a92F14A85daBF6FF27b07362Cae", + "0x2B888954421b424C5D3D9Ce9bB67c9bD47537d12", + "0xFe45baf0F18c207152A807c1b05926583CFE2e4b", + "0x962228a90eaC69238c7D1F216d80037e61eA9255", + "0x700F1a984C962b447CcDb95c4c2D8074C65098a3" + ], + "onChainOwners": [ + "0xf5D3dbda5F41A0E26D71B948e29522398e71cFaE", + "0xeA6c0837fef621E77329f85820F503cA09f2B3a9", + "0x6F2A8Ee9452ba7d336b3fba03caC27f7818AeAD6", + "0x2B888954421b424C5D3D9Ce9bB67c9bD47537d12", + "0xFe45baf0F18c207152A807c1b05926583CFE2e4b", + "0x962228a90eaC69238c7D1F216d80037e61eA9255", + "0x70aF5a3368606c6557D2B3ce2EEC8796B914EAa3", + "0x5Db9926c93085a92F14A85daBF6FF27b07362Cae", + "0x700F1a984C962b447CcDb95c4c2D8074C65098a3" + ], + "docsSourcePath": "docs/developers/security/multisig.md" } }, "addressChecks": { "allV3ChecksPassed": true, "allVeYfiChecksPassed": true, + "allMultisigChecksPassed": true, "failedChecks": [], "v3Checks": { "topLevel": { @@ -128,6 +155,14 @@ "yGauge DAI-2 yVault": true, "yGauge WETH-2 yVault": true, "yGauge crvUSD-2 yVault": true + }, + "multisigChecks": { + "docsMembersSectionParsed": true, + "docsAddressesValid": true, + "docsOwnerCountMatch": true, + "docsUniqueOwnersCheck": true, + "onChainUniqueOwnersCheck": true, + "exactMembersMatch": true } } } \ No newline at end of file diff --git a/scripts/runAddressChecks.ts b/scripts/runAddressChecks.ts index f63f988f7..3dfecd31c 100644 --- a/scripts/runAddressChecks.ts +++ b/scripts/runAddressChecks.ts @@ -8,6 +8,7 @@ import { fetchAndCheckYearnV3Addresses, } from '../src/ethereum/v3Checks' import { veYfiChecks } from '../src/ethereum/veYfiChecks' +import { checkYearnMultisigMembers } from '../src/ethereum/multisigChecks' import { yfiContracts, veYfiContracts } from '../src/ethereum/constants' import { ContractAddresses, @@ -18,7 +19,13 @@ import { mainnet } from 'viem/chains' dotenv.config() -const alchemyKey = process.env.ALCHEMY_API_KEY +const alchemyKey = process.env.ALCHEMY_API_KEY?.trim() +const invalidAlchemyValues = new Set(['', 'undefined', 'null', 'yourApiKeyHere']) + +if (!alchemyKey || invalidAlchemyValues.has(alchemyKey)) { + console.error('Environment vars not set properly') + process.exit(1) +} const publicClient = createPublicClient({ batch: { @@ -90,16 +97,28 @@ const fetchAddresses = async () => { veYfiCheckFlag = veYfiData?.checkFlag if (!veYfiData) throw new Error('Failed to fetch veYFI gauge addresses') + let multisigCheckFlag: boolean | undefined + multisigCheckFlag = true + const multisigData = await checkYearnMultisigMembers( + publicClient, + multisigCheckFlag, + failedChecks + ) + multisigCheckFlag = multisigData?.checkFlag + if (!multisigData) throw new Error('Failed to fetch multisig owners') + const addressesData: ContractAddresses = { v3ContractAddresses: v3AddressData, yfiTokenContracts: yfiContracts, veYfiContracts: veYfiContracts, veYfiGaugeAddresses: veYfiData.veYfiGaugeAddresses, + yearnMultisigMembers: multisigData.addresses, } const addressChecks: AddressChecks = { allV3ChecksPassed: v3CheckFlag, allVeYfiChecksPassed: veYfiCheckFlag, + allMultisigChecksPassed: multisigCheckFlag, failedChecks, v3Checks: { topLevel: topLevelData.checks, @@ -108,12 +127,15 @@ const fetchAddresses = async () => { yearnV3: yearnV3Data.checks, }, veYfiChecks: veYfiData.veYfiGaugeChecks, + multisigChecks: multisigData.checks, } if ( v3CheckFlag === false || v3CheckFlag === undefined || veYfiCheckFlag === false || - veYfiCheckFlag === undefined + veYfiCheckFlag === undefined || + multisigCheckFlag === false || + multisigCheckFlag === undefined ) { console.log('Addresses:', addressesData) console.log('Checks:', addressChecks) @@ -141,7 +163,9 @@ async function runAddressCheck() { } const allChecksPassed = - addressChecks.allV3ChecksPassed && addressChecks.allVeYfiChecksPassed + addressChecks.allV3ChecksPassed && + addressChecks.allVeYfiChecksPassed && + addressChecks.allMultisigChecksPassed process.env.ALL_CHECKS_PASSED = allChecksPassed ? 'true' : 'false' console.log('allChecksPassed: ', process.env.ALL_CHECKS_PASSED) @@ -156,7 +180,7 @@ async function runAddressCheck() { issueContent += `- ${check}\n` }) issueContent += - '\nThe addresses shown above should be the updated, correct addresses. Please review and change the values in `src/ethereum/constants.ts`.\n' + '\nThe addresses shown above should be the updated, correct addresses. Please review and update the relevant source (`src/ethereum/constants.ts` and/or `docs/developers/security/multisig.md`).\n' fs.writeFileSync('issue_body.md', issueContent) console.log('Issue content generated.') diff --git a/src/ethereum/ABIs/gnosisSafeABI.ts b/src/ethereum/ABIs/gnosisSafeABI.ts new file mode 100644 index 000000000..9cca1cf6d --- /dev/null +++ b/src/ethereum/ABIs/gnosisSafeABI.ts @@ -0,0 +1,9 @@ +export const gnosisSafeABI = [ + { + stateMutability: 'view', + type: 'function', + name: 'getOwners', + inputs: [], + outputs: [{ name: '', type: 'address[]' }], + }, +] diff --git a/src/ethereum/ABIs/index.ts b/src/ethereum/ABIs/index.ts index cf8c223a9..66d005569 100644 --- a/src/ethereum/ABIs/index.ts +++ b/src/ethereum/ABIs/index.ts @@ -14,3 +14,4 @@ export * from './v3VaultFactoryBlueprintABI' export * from './veyfiABI' export * from './yPoolsGenericGovernorABI' export * from './yPoolsInclusionVoteABI' +export * from './gnosisSafeABI' diff --git a/src/ethereum/multisigCalls.ts b/src/ethereum/multisigCalls.ts new file mode 100644 index 000000000..3f0eb7489 --- /dev/null +++ b/src/ethereum/multisigCalls.ts @@ -0,0 +1,18 @@ +import { Address, PublicClient, getContract } from 'viem' +import { gnosisSafeABI } from './ABIs' + +export const readSafeOwners = async ( + safeAddress: Address, + publicClient: PublicClient +) => { + const contract = getContract({ + address: safeAddress, + abi: gnosisSafeABI, + client: publicClient, + }) + + console.log('Fetching Gnosis Safe owners...') + const owners = await contract.read.getOwners() + console.log('Gnosis Safe owners fetched.') + return owners +} diff --git a/src/ethereum/multisigChecks.ts b/src/ethereum/multisigChecks.ts new file mode 100644 index 000000000..4fe17dfb4 --- /dev/null +++ b/src/ethereum/multisigChecks.ts @@ -0,0 +1,163 @@ +import fs from 'fs' +import path from 'path' +import { Address, PublicClient, getAddress } from 'viem' +import * as constants from './constants' +import { readSafeOwners } from './multisigCalls' + +const MULTISIG_DOCS_PATH = 'docs/developers/security/multisig.md' + +const extractMembersSection = (markdown: string) => { + const parts = markdown.split(/^## Members\s*$/m) + const afterMembersHeading = parts[1] + if (!afterMembersHeading) { + throw new Error('Failed to find "## Members" section in multisig docs') + } + const nextHeadingIndex = afterMembersHeading.search(/^##\s/m) + if (nextHeadingIndex === -1) { + return afterMembersHeading + } + return afterMembersHeading.slice(0, nextHeadingIndex) +} + +const extractAddressesFromMembersTable = (section: string) => { + const addresses: string[] = [] + for (const line of section.split(/\r?\n/)) { + if (!line.trim().startsWith('|')) continue + const matches = line.match(/0x[a-fA-F0-9]{40}/g) + if (!matches || matches.length === 0) continue + addresses.push(matches[0]) + } + return addresses +} + +const getDuplicates = (addresses: Address[]) => { + const seen = new Set
() + const duplicates = new Set
() + for (const address of addresses) { + if (seen.has(address)) { + duplicates.add(address) + continue + } + seen.add(address) + } + return [...duplicates] +} + +export const checkYearnMultisigMembers = async ( + publicClient: PublicClient, + checkFlag: boolean | undefined, + failedChecks: string[] +) => { + const multisigAddress = getAddress(constants.yearnV3ContractsMainnet.daddy) + const docsPath = path.resolve(MULTISIG_DOCS_PATH) + + console.log('validating Yearn multisig members...') + + let docsMembersSectionParsed = true + let docsAddressesValid = true + let docsOwnerCountMatch = true + let docsUniqueOwnersCheck = true + let onChainUniqueOwnersCheck = true + let exactMembersMatch = true + + let docsMemberAddressesRaw: string[] = [] + let docsMemberAddresses: Address[] = [] + + try { + const markdown = fs.readFileSync(docsPath, 'utf8') + const membersSection = extractMembersSection(markdown) + docsMemberAddressesRaw = extractAddressesFromMembersTable(membersSection) + } catch (error) { + docsMembersSectionParsed = false + exactMembersMatch = false + failedChecks.push('yearnMultisig docs members section parse failed') + console.error(error) + } + + if (docsMembersSectionParsed) { + try { + docsMemberAddresses = docsMemberAddressesRaw.map((address) => + getAddress(address) + ) + } catch (error) { + docsAddressesValid = false + exactMembersMatch = false + failedChecks.push('yearnMultisig docs contains invalid address') + console.error(error) + } + } + + const onChainOwners = (await readSafeOwners(multisigAddress, publicClient)).map( + (address) => getAddress(address) + ) + + if (docsAddressesValid) { + docsOwnerCountMatch = docsMemberAddresses.length === onChainOwners.length + if (!docsOwnerCountMatch) { + failedChecks.push( + `yearnMultisig owner count mismatch (docs=${docsMemberAddresses.length}, onchain=${onChainOwners.length})` + ) + } + + const docsDuplicates = getDuplicates(docsMemberAddresses) + const onChainDuplicates = getDuplicates(onChainOwners) + docsUniqueOwnersCheck = docsDuplicates.length === 0 + onChainUniqueOwnersCheck = onChainDuplicates.length === 0 + + for (const duplicate of docsDuplicates) { + failedChecks.push(`yearnMultisig duplicate docs member: ${duplicate}`) + } + for (const duplicate of onChainDuplicates) { + failedChecks.push(`yearnMultisig duplicate onchain owner: ${duplicate}`) + } + + const docsSet = new Set(docsMemberAddresses) + const onChainSet = new Set(onChainOwners) + const missingInDocs = onChainOwners.filter((owner) => !docsSet.has(owner)) + const extraInDocs = docsMemberAddresses.filter( + (owner) => !onChainSet.has(owner) + ) + + if (missingInDocs.length > 0 || extraInDocs.length > 0) { + exactMembersMatch = false + } + + for (const owner of missingInDocs) { + failedChecks.push(`yearnMultisig missing in docs: ${owner}`) + } + for (const owner of extraInDocs) { + failedChecks.push(`yearnMultisig extra in docs: ${owner}`) + } + } + + if ( + !docsMembersSectionParsed || + !docsAddressesValid || + !docsOwnerCountMatch || + !docsUniqueOwnersCheck || + !onChainUniqueOwnersCheck || + !exactMembersMatch + ) { + checkFlag = false + } + + console.log('Yearn multisig member validation complete. \n') + + return { + addresses: { + multisigAddress, + docsMemberAddresses, + onChainOwners, + docsSourcePath: MULTISIG_DOCS_PATH, + }, + checks: { + docsMembersSectionParsed, + docsAddressesValid, + docsOwnerCountMatch, + docsUniqueOwnersCheck, + onChainUniqueOwnersCheck, + exactMembersMatch, + }, + checkFlag, + } +} diff --git a/src/ethereum/types.ts b/src/ethereum/types.ts index 191ef47fa..e18d7b2f1 100644 --- a/src/ethereum/types.ts +++ b/src/ethereum/types.ts @@ -3,6 +3,7 @@ export type ContractAddresses = { yfiTokenContracts: YfiTokenContracts veYfiContracts: VeYfiContracts veYfiGaugeAddresses: GaugeAddressRecord + yearnMultisigMembers: YearnMultisigMembers } export type V3ContractAddresses = { @@ -60,6 +61,7 @@ export type VeYfiContracts = { export type GaugeAddressRecord = Record export type GaugeCheckRecord = Record +export type MultisigAddressList = `0x${string}`[] export type VeYfiGauge = { index: number @@ -70,9 +72,26 @@ export type VeYfiGauge = { underlyingVaultAddress: `0x${string}` | string } +export type YearnMultisigMembers = { + multisigAddress: `0x${string}` + docsMemberAddresses: MultisigAddressList + onChainOwners: MultisigAddressList + docsSourcePath: string +} + +export type MultisigChecks = { + docsMembersSectionParsed: boolean + docsAddressesValid: boolean + docsOwnerCountMatch: boolean + docsUniqueOwnersCheck: boolean + onChainUniqueOwnersCheck: boolean + exactMembersMatch: boolean +} + export type AddressChecks = { allV3ChecksPassed: boolean | undefined allVeYfiChecksPassed: boolean | undefined + allMultisigChecksPassed: boolean | undefined failedChecks: string[] v3Checks: { topLevel: { @@ -88,4 +107,5 @@ export type AddressChecks = { yearnV3: { [key: string]: boolean } } veYfiChecks: GaugeCheckRecord + multisigChecks: MultisigChecks } From 4d69fce95c365c462689b3cb5af6c239a290fb5d Mon Sep 17 00:00:00 2001 From: engn33r Date: Tue, 24 Feb 2026 00:00:00 +0000 Subject: [PATCH 2/4] Update dependencies --- bun.lock | 20 +++++++++++++++----- package.json | 8 +++++--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/bun.lock b/bun.lock index 9cdcefee7..abfc1b296 100644 --- a/bun.lock +++ b/bun.lock @@ -16,6 +16,8 @@ "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", + "baseline-browser-mapping": "^2.10.0", + "caniuse-lite": "^1.0.30001774", "clsx": "1.1.1", "dotenv": "^16.6.1", "hast-util-is-element": "1.1.0", @@ -1008,7 +1010,7 @@ "@xtuc/long": ["@xtuc/long@4.2.2", "", {}, "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="], - "abitype": ["abitype@1.1.0", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A=="], + "abitype": ["abitype@1.2.3", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg=="], "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], @@ -1090,6 +1092,8 @@ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="], + "batch": ["batch@0.6.1", "", {}, "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw=="], "big.js": ["big.js@5.2.2", "", {}, "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="], @@ -1140,7 +1144,7 @@ "caniuse-api": ["caniuse-api@3.0.0", "", { "dependencies": { "browserslist": "^4.0.0", "caniuse-lite": "^1.0.0", "lodash.memoize": "^4.1.2", "lodash.uniq": "^4.5.0" } }, "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw=="], - "caniuse-lite": ["caniuse-lite@1.0.30001737", "", {}, "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw=="], + "caniuse-lite": ["caniuse-lite@1.0.30001774", "", {}, "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA=="], "cardinal": ["cardinal@2.1.1", "", { "dependencies": { "ansicolors": "~0.3.2", "redeyed": "~2.1.0" }, "bin": { "cdl": "./bin/cdl.js" } }, "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw=="], @@ -2228,7 +2232,7 @@ "os-tmpdir": ["os-tmpdir@1.0.2", "", {}, "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="], - "ox": ["ox@0.9.6", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.0.9", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-8SuCbHPvv2eZLYXrNmC0EC12rdzXQLdhnOMlHDW2wiCPLxBrOOJwX5L5E61by+UjTPOryqQiRSnjIKCI+GykKg=="], + "ox": ["ox@0.12.4", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-+P+C7QzuwPV8lu79dOwjBKfB2CbnbEXe/hfyyrff1drrO1nOOj3Hc87svHfcW1yneRr3WXaKr6nz11nq+/DF9Q=="], "p-cancelable": ["p-cancelable@1.1.0", "", {}, "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw=="], @@ -2712,7 +2716,7 @@ "sockjs": ["sockjs@0.3.24", "", { "dependencies": { "faye-websocket": "^0.11.3", "uuid": "^8.3.2", "websocket-driver": "^0.7.4" } }, "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ=="], - "solc": ["solc@0.8.31", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-wpccgDgu/aE/rRcF2F/LeN+4knK0734XTcjppyaQOticjYd/Giq1AJE3XPQZKEViAsY3sNaFKl7QpMRYrK35vg=="], + "solc": ["solc@0.8.34", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-qf8HajA1sHhXRV0hMSDXLjVbc4v3Q+SQbL9zok+1WmgVj7Z4oMjMHxaysCzfGtFVqjZdfDDJWyZI+tcx5bO7Dw=="], "solidity-docgen": ["solidity-docgen@0.5.17", "", { "dependencies": { "@oclif/command": "^1.8.0", "@oclif/config": "^1.17.0", "@oclif/errors": "^1.3.3", "@oclif/plugin-help": "^5.0.0", "globby": "^11.0.0", "handlebars": "^4.7.6", "json5": "^2.1.3", "lodash": "^4.17.15", "micromatch": "^4.0.2", "minimatch": "^5.0.0", "semver": "^7.3.2", "solc": "^0.6.7" }, "bin": { "solidity-docgen": "dist/cli.js" } }, "sha512-RX5SPLFL9z0ZVBcZ/o5l/TKXMgSjNhWdumLuuv+Dy1O/66sThpHYd0HVpzdwAjVff0Ajk76bYM2zZYiMnqBfng=="], @@ -2928,7 +2932,7 @@ "victory-vendor": ["victory-vendor@36.9.2", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ=="], - "viem": ["viem@2.41.2", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.1.0", "isows": "1.0.7", "ox": "0.9.6", "ws": "8.18.3" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-LYliajglBe1FU6+EH9mSWozp+gRA/QcHfxeD9Odf83AdH5fwUS7DroH4gHvlv6Sshqi1uXrYFA2B/EOczxd15g=="], + "viem": ["viem@2.46.3", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", "ox": "0.12.4", "ws": "8.18.3" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-2LJS+Hyh2sYjHXQtzfv1kU9pZx9dxFzvoU/ZKIcn0FNtOU0HQuIICuYdWtUDFHaGXbAdVo8J1eCvmjkL9JVGwg=="], "vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="], @@ -3168,6 +3172,8 @@ "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], + "autoprefixer/caniuse-lite": ["caniuse-lite@1.0.30001737", "", {}, "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw=="], + "babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "body-parser/bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], @@ -3180,12 +3186,16 @@ "boxen/type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="], + "browserslist/caniuse-lite": ["caniuse-lite@1.0.30001737", "", {}, "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw=="], + "cacheable-request/get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], "cacheable-request/lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="], "cacheable-request/responselike": ["responselike@1.0.2", "", { "dependencies": { "lowercase-keys": "^1.0.0" } }, "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ=="], + "caniuse-api/caniuse-lite": ["caniuse-lite@1.0.30001737", "", {}, "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw=="], + "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "clean-stack/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], diff --git a/package.json b/package.json index 96b4f478f..089236b50 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,8 @@ "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", + "baseline-browser-mapping": "^2.10.0", + "caniuse-lite": "^1.0.30001774", "clsx": "1.1.1", "dotenv": "^16.6.1", "hast-util-is-element": "1.1.0", @@ -43,11 +45,11 @@ "recharts": "^2.15.4", "rehype-katex": "^7.0.1", "remark-math": "^6.0.0", - "solc": "^0.8.31", + "solc": "^0.8.34", "solidity-docgen": "^0.5.17", - "turndown": "^7.1.2", + "turndown": "^7.2.2", "turndown-plugin-gfm": "^1.0.2", - "viem": "^2.41.2" + "viem": "^2.46.3" }, "browserslist": { "production": [ From cedb7a8e1b89e999450a8af6998dc56bfb7d35a6 Mon Sep 17 00:00:00 2001 From: Ross Date: Tue, 24 Feb 2026 14:24:31 -0700 Subject: [PATCH 3/4] update multisig check logic --- scripts/fetchedAddressData.json | 2 +- scripts/runAddressChecks.ts | 1 + src/ethereum/multisigChecks.ts | 41 ++++++++++++++++++++++++++++----- src/ethereum/v3Checks.ts | 12 +++++++++- 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/scripts/fetchedAddressData.json b/scripts/fetchedAddressData.json index 95239fb78..988be8834 100644 --- a/scripts/fetchedAddressData.json +++ b/scripts/fetchedAddressData.json @@ -1,5 +1,5 @@ { - "timeLastChecked": 1771939655, + "timeLastChecked": 1771960246, "addressesData": { "v3ContractAddresses": { "topLevel": { diff --git a/scripts/runAddressChecks.ts b/scripts/runAddressChecks.ts index 3dfecd31c..9d3a258fd 100644 --- a/scripts/runAddressChecks.ts +++ b/scripts/runAddressChecks.ts @@ -100,6 +100,7 @@ const fetchAddresses = async () => { let multisigCheckFlag: boolean | undefined multisigCheckFlag = true const multisigData = await checkYearnMultisigMembers( + yearnV3Data.addresses.yearnDaddy, publicClient, multisigCheckFlag, failedChecks diff --git a/src/ethereum/multisigChecks.ts b/src/ethereum/multisigChecks.ts index 4fe17dfb4..977951233 100644 --- a/src/ethereum/multisigChecks.ts +++ b/src/ethereum/multisigChecks.ts @@ -1,10 +1,10 @@ import fs from 'fs' import path from 'path' import { Address, PublicClient, getAddress } from 'viem' -import * as constants from './constants' import { readSafeOwners } from './multisigCalls' const MULTISIG_DOCS_PATH = 'docs/developers/security/multisig.md' +const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' const extractMembersSection = (markdown: string) => { const parts = markdown.split(/^## Members\s*$/m) @@ -44,11 +44,14 @@ const getDuplicates = (addresses: Address[]) => { } export const checkYearnMultisigMembers = async ( + multisigAddressFromRoleManager: Address | undefined, publicClient: PublicClient, checkFlag: boolean | undefined, failedChecks: string[] ) => { - const multisigAddress = getAddress(constants.yearnV3ContractsMainnet.daddy) + const multisigAddress = getAddress( + multisigAddressFromRoleManager ?? ZERO_ADDRESS + ) const docsPath = path.resolve(MULTISIG_DOCS_PATH) console.log('validating Yearn multisig members...') @@ -59,6 +62,7 @@ export const checkYearnMultisigMembers = async ( let docsUniqueOwnersCheck = true let onChainUniqueOwnersCheck = true let exactMembersMatch = true + let onChainOwnersRead = true let docsMemberAddressesRaw: string[] = [] let docsMemberAddresses: Address[] = [] @@ -87,11 +91,36 @@ export const checkYearnMultisigMembers = async ( } } - const onChainOwners = (await readSafeOwners(multisigAddress, publicClient)).map( - (address) => getAddress(address) - ) + let onChainOwners: Address[] = [] + + if (multisigAddress === getAddress(ZERO_ADDRESS)) { + onChainOwnersRead = false + docsOwnerCountMatch = false + onChainUniqueOwnersCheck = false + exactMembersMatch = false + checkFlag = false + failedChecks.push('yearnMultisig daddy address missing from role manager') + } else { + try { + const safeOwners = (await readSafeOwners( + multisigAddress, + publicClient + )) as Address[] + onChainOwners = safeOwners.map((address) => getAddress(address)) + } catch (error) { + onChainOwnersRead = false + docsOwnerCountMatch = false + onChainUniqueOwnersCheck = false + exactMembersMatch = false + checkFlag = false + failedChecks.push( + `yearnMultisig failed to read onchain owners for ${multisigAddress}` + ) + console.error(error) + } + } - if (docsAddressesValid) { + if (docsAddressesValid && onChainOwnersRead) { docsOwnerCountMatch = docsMemberAddresses.length === onChainOwners.length if (!docsOwnerCountMatch) { failedChecks.push( diff --git a/src/ethereum/v3Checks.ts b/src/ethereum/v3Checks.ts index 73d276255..41c7a48e5 100644 --- a/src/ethereum/v3Checks.ts +++ b/src/ethereum/v3Checks.ts @@ -309,6 +309,8 @@ export const fetchAndCheckYearnV3Addresses = async ( console.log('validating Yearn specific V3 periphery addresses...') const yearnAccountant = addresses.yearnAccountant || '0x0000000000000000000000000000000000000000' + const yearnDaddy = + addresses.yearnDaddy || '0x0000000000000000000000000000000000000000' const yearnRegistry = addresses.yearnRegistry || '0x0000000000000000000000000000000000000000' const yearnDebtAllocator = @@ -348,13 +350,20 @@ export const fetchAndCheckYearnV3Addresses = async ( yearnDebtAllocator, failedChecks ) + const daddyCheck = await validateAddress( + constants.yearnV3ContractsMainnet.daddy, + 'yearnV3Daddy', + yearnDaddy, + failedChecks + ) if ( !accountantCheck || !accountantENSCheck || !registryCheck || !registryENSCheck || - !debtAllocatorCheck + !debtAllocatorCheck || + !daddyCheck ) { checkFlag = false } @@ -363,6 +372,7 @@ export const fetchAndCheckYearnV3Addresses = async ( accountantCheck, registryCheck, debtAllocatorCheck, + daddyCheck, } console.log('Yearn V3 Periphery address validation complete. \n') return { addresses, checks, checkFlag } From 0a7ad0ff55a1e94eef90482690c84e7793641ca9 Mon Sep 17 00:00:00 2001 From: Ross Date: Tue, 24 Feb 2026 14:45:45 -0700 Subject: [PATCH 4/4] remove unneeded deps --- bun.lock | 19 ++++--------------- package.json | 2 -- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/bun.lock b/bun.lock index abfc1b296..588a7a898 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,5 @@ { "lockfileVersion": 1, - "configVersion": 0, "workspaces": { "": { "name": "yearn-devdocs", @@ -16,8 +15,6 @@ "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", - "baseline-browser-mapping": "^2.10.0", - "caniuse-lite": "^1.0.30001774", "clsx": "1.1.1", "dotenv": "^16.6.1", "hast-util-is-element": "1.1.0", @@ -27,11 +24,11 @@ "recharts": "^2.15.4", "rehype-katex": "^7.0.1", "remark-math": "^6.0.0", - "solc": "^0.8.31", + "solc": "^0.8.34", "solidity-docgen": "^0.5.17", - "turndown": "^7.1.2", + "turndown": "^7.2.2", "turndown-plugin-gfm": "^1.0.2", - "viem": "^2.41.2", + "viem": "^2.46.3", }, "devDependencies": { "@docusaurus/module-type-aliases": "3.9.2", @@ -1092,8 +1089,6 @@ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="], - "batch": ["batch@0.6.1", "", {}, "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw=="], "big.js": ["big.js@5.2.2", "", {}, "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="], @@ -1144,7 +1139,7 @@ "caniuse-api": ["caniuse-api@3.0.0", "", { "dependencies": { "browserslist": "^4.0.0", "caniuse-lite": "^1.0.0", "lodash.memoize": "^4.1.2", "lodash.uniq": "^4.5.0" } }, "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw=="], - "caniuse-lite": ["caniuse-lite@1.0.30001774", "", {}, "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA=="], + "caniuse-lite": ["caniuse-lite@1.0.30001737", "", {}, "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw=="], "cardinal": ["cardinal@2.1.1", "", { "dependencies": { "ansicolors": "~0.3.2", "redeyed": "~2.1.0" }, "bin": { "cdl": "./bin/cdl.js" } }, "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw=="], @@ -3172,8 +3167,6 @@ "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], - "autoprefixer/caniuse-lite": ["caniuse-lite@1.0.30001737", "", {}, "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw=="], - "babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "body-parser/bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], @@ -3186,16 +3179,12 @@ "boxen/type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="], - "browserslist/caniuse-lite": ["caniuse-lite@1.0.30001737", "", {}, "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw=="], - "cacheable-request/get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], "cacheable-request/lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="], "cacheable-request/responselike": ["responselike@1.0.2", "", { "dependencies": { "lowercase-keys": "^1.0.0" } }, "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ=="], - "caniuse-api/caniuse-lite": ["caniuse-lite@1.0.30001737", "", {}, "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw=="], - "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "clean-stack/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], diff --git a/package.json b/package.json index 089236b50..34b019f7c 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,6 @@ "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", - "baseline-browser-mapping": "^2.10.0", - "caniuse-lite": "^1.0.30001774", "clsx": "1.1.1", "dotenv": "^16.6.1", "hast-util-is-element": "1.1.0",