Skip to content

Commit a5c31d0

Browse files
committed
feat: Soften duplicate contract name in the case contracts have identical ABIs
Foundry seems to duplicate some imported sub-files within a contract, for example: `<foundry root>/src/parts/primitives/Foo.sol` Is defined only once, and imported from `<foundry root>/src/Bar.sol` But ends up appearing twice in artifacts directory: ``` <foundry root>/out/Foo.sol/Foo.json <foundry root>/out/primitives/Foo.sol/Foo.json ``` In this case `getArtifactPaths()` in the Foundry plugin pick up the same contracts twice since they end up existing in multiple subdirectories of `out/`. I don't think there is a reliable to heuristic to de-duplicate them at this level. It seems like duck-typing contracts by the ABI should be fine and may help other plugin sources. it could always be disabled by default and enabled via an option at the cost of yet more config Signed-off-by: Silas Davis <[email protected]>
1 parent f5b717c commit a5c31d0

File tree

1 file changed

+18
-3
lines changed

1 file changed

+18
-3
lines changed

packages/cli/src/commands/generate.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { watch } from 'chokidar'
66
import { default as dedent } from 'dedent'
77
import { basename, dirname, resolve } from 'pathe'
88
import pc from 'picocolors'
9-
import { type Abi, type Address, getAddress } from 'viem'
9+
import { type Abi, type Address, getAddress, keccak256 } from 'viem'
1010
import { z } from 'zod'
1111

1212
import type { Contract, ContractConfig, Plugin, Watch } from '../config.js'
@@ -98,10 +98,19 @@ export async function generate(options: Generate = {}) {
9898
const contractNames = new Set<string>()
9999
const contractMap = new Map<string, Contract>()
100100
for (const contractConfig of contractConfigs) {
101-
if (contractNames.has(contractConfig.name))
101+
const previouslySeenContract = contractMap.get(contractConfig.name)
102+
if (previouslySeenContract) {
103+
if (
104+
hashAbi(previouslySeenContract.abi) === hashAbi(contractConfig.abi)
105+
) {
106+
// If the contract name and ABI match, skip adding it again, but allow it since this can occur when generating
107+
// from sources that mutually import each other, such as peer Foundry projects.
108+
continue
109+
}
102110
throw new Error(
103-
`Contract name "${contractConfig.name}" must be unique.`,
111+
`Duplicate contract name "${contractConfig.name}" found with different ABI. Contract names must be unique up to ABI.`,
104112
)
113+
}
105114
const contract = await getContract({ ...contractConfig, isTypeScript })
106115
contractMap.set(contract.name, contract)
107116

@@ -407,3 +416,9 @@ function getBannerContent({ name }: { name: string }) {
407416
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
408417
`
409418
}
419+
420+
function hashAbi(abi: Abi): string {
421+
// Could use something faster, e.g. non-cryptographic hash or CRC32, but only called in the case
422+
// we hit duplicate contract names, so probably not worth it.
423+
return keccak256(Buffer.from(JSON.stringify(abi)))
424+
}

0 commit comments

Comments
 (0)