Skip to content

Commit

Permalink
fix(pg): Detect ambiguous contract names in configs when using foundry
Browse files Browse the repository at this point in the history
  • Loading branch information
RPate97 committed Jun 26, 2023
1 parent ab3a11c commit 83340e8
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 22 deletions.
5 changes: 5 additions & 0 deletions .changeset/tough-spiders-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chugsplash/plugins': patch
---

Detect ambiguous contract names in foundry
92 changes: 70 additions & 22 deletions packages/plugins/src/foundry/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,18 @@ export const getBuildInfo = (

export const getContractArtifact = async (
name: string,
artifactFilder: string
artifactFilder: string,
cachedContractNames: Record<string, string[]>
): Promise<ContractArtifact> => {
const sources = cachedContractNames[name]
if (sources?.length > 1) {
throw new Error(
`Detected multiple contracts with the name ${name} in different files, to resolve this:
- Use the fully qualified name for this contract: 'path/to/file/SomeFile.sol:MyContract'
- Or rename one of the contracts and force recompile: 'forge build --force'`
)
}

// Try to find the artifact in the standard format
const folderName = `${name}.sol`
const fileName = `${name}.json`
Expand Down Expand Up @@ -103,16 +113,26 @@ export const makeGetConfigArtifacts = (
}

const buildInfoCacheFilePath = join(cachePath, 'chugsplash-cache.json')
let buildInfoCache: Record<
string,
{
name: string
time: number
contracts: string[]
}
> = fs.existsSync(buildInfoCacheFilePath)
let buildInfoCache: {
// We track all contract names and the associated source files that contain them
// This allows us to detect ambiguous contract names and prompt the user to use fully qualified names
contracts: Record<string, string[]>
// We keep track of the last modified time in each build info file so we can easily find the most recently generated build info files
// We also keep track of all the contract files output by each build info file, so we can easily look up the required file for each contract artifact
files: Record<
string,
{
name: string
time: number
contracts: string[]
}
>
} = fs.existsSync(buildInfoCacheFilePath)
? JSON.parse(fs.readFileSync(buildInfoCacheFilePath, 'utf8'))
: {}
: {
contracts: {},
files: {},
}

const buildInfoPath = join(buildInfoFolder)

Expand All @@ -123,9 +143,19 @@ export const makeGetConfigArtifacts = (
return fileName.endsWith('.json')
})

// If there are no build info files, then clear the cache
if (buildInfoFileNames.length === 0) {
buildInfoCache = {}
const cachedNames = Object.keys(buildInfoCache.files)
// If there is only one build info file and it is not in the cache,
// then clear the cache b/c the user must have force recompiled
if (
buildInfoFileNames.length === 1 &&
(!cachedNames.includes(buildInfoFileNames[0]) ||
// handles an edge case where the user made a change and then reverted it and force recompiled
buildInfoFileNames.length > 1)
) {
buildInfoCache = {
contracts: {},
files: {},
}
}

const buildInfoFileNamesWithTime = buildInfoFileNames
Expand All @@ -140,24 +170,41 @@ export const makeGetConfigArtifacts = (
const localBuildInfoCache = {}
await Promise.all(
buildInfoFileNamesWithTime
.filter((file) => buildInfoCache[file.name]?.time !== file.time)
.filter((file) => buildInfoCache.files[file.name]?.time !== file.time)
.map(async (file) => {
// If the file exists in the cache and the time has changed, then we just update the time
if (
buildInfoCache[file.name]?.time &&
buildInfoCache[file.name]?.time !== file.time
buildInfoCache.files[file.name]?.time &&
buildInfoCache.files[file.name]?.time !== file.time
) {
buildInfoCache[file.name].time = file.time
buildInfoCache.files[file.name].time = file.time
return
}

const buildInfo = JSON.parse(
fs.readFileSync(join(buildInfoFolder, file.name), 'utf8')
)

// We keep track of the last modified time in each build info file so we can easily fine the most recently generated build info files
// We also keep track of all the contracts output by each build info file, so we can easily look up the required file for each source name
buildInfoCache[file.name] = {
// Update the contract name to source file dictionary in the cache
Object.keys(buildInfo.output.contracts).map((contractSourceName) => {
const contractOutput =
buildInfo.output.contracts[contractSourceName]
const contractNames = Object.keys(contractOutput)
contractNames.map((contractName) => {
if (!buildInfoCache.contracts[contractName]) {
buildInfoCache.contracts[contractName] = [contractSourceName]
} else if (
!buildInfoCache.contracts[contractName].includes(
contractSourceName
)
) {
buildInfoCache.contracts[contractName].push(contractSourceName)
}
})
})

// Update the build info file dictionary in the cache
buildInfoCache.files[file.name] = {
name: file.name,
time: file.time,
contracts: Object.keys(buildInfo.output.contracts),
Expand All @@ -167,7 +214,7 @@ export const makeGetConfigArtifacts = (
})
)
// Just make sure the files are sorted by time
const sortedCachedFiles = Object.values(buildInfoCache).sort(
const sortedCachedFiles = Object.values(buildInfoCache.files).sort(
(a, b) => b.time - a.time
)

Expand All @@ -178,7 +225,8 @@ export const makeGetConfigArtifacts = (
async ([referenceName, contractConfig]) => {
const artifact = await getContractArtifact(
contractConfig.contract,
artifactFolder
artifactFolder,
buildInfoCache.contracts
)

// Look through the cahce for the first build info file that contains the contract
Expand Down

0 comments on commit 83340e8

Please sign in to comment.