diff --git a/packages/core/contracts/test/NamespacedConflicts.sol b/packages/core/contracts/test/NamespacedConflictsStructsOnly.sol similarity index 100% rename from packages/core/contracts/test/NamespacedConflicts.sol rename to packages/core/contracts/test/NamespacedConflictsStructsOnly.sol diff --git a/packages/core/contracts/test/NamespacedConflictsWithFunctions.sol b/packages/core/contracts/test/NamespacedConflictsWithFunctions.sol new file mode 100644 index 000000000..e307f28c8 --- /dev/null +++ b/packages/core/contracts/test/NamespacedConflictsWithFunctions.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract DuplicateNamespace { + function foo() public pure returns (uint256) { + return 0; + } + + /// @custom:storage-location erc7201:conflicting + struct Conflicting1 { + uint256 b; + } + + /// @custom:storage-location erc7201:conflicting + struct Conflicting2 { + uint256 c; + } + + function foo2() public pure returns (uint256) { + return 0; + } +} + +contract ConflictsWithParent is DuplicateNamespace { + function foo3() public pure returns (uint256) { + return 0; + } + + /// @custom:storage-location erc7201:conflicting + struct Conflicting { + uint256 a; + } + + function foo4() public pure returns (uint256) { + return 0; + } +} + +contract ConflictsInBothParents is DuplicateNamespace, ConflictsWithParent { +} \ No newline at end of file diff --git a/packages/core/contracts/test/NamespacedConflictsWithVariables.sol b/packages/core/contracts/test/NamespacedConflictsWithVariables.sol new file mode 100644 index 000000000..9d209c344 --- /dev/null +++ b/packages/core/contracts/test/NamespacedConflictsWithVariables.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract DuplicateNamespace { + /// @custom:storage-location erc7201:conflicting + struct Conflicting1 { + uint256 b; + } Conflicting1 $Conflicting1; + + /// @custom:storage-location erc7201:conflicting + struct Conflicting2 { + uint256 c; + } Conflicting2 $Conflicting2; +} + +contract ConflictsWithParent is DuplicateNamespace { + /// @custom:storage-location erc7201:conflicting + struct Conflicting { + uint256 a; + } Conflicting $Conflicting; +} + +contract ConflictsInBothParents is DuplicateNamespace, ConflictsWithParent { +} \ No newline at end of file diff --git a/packages/core/hardhat.config.js b/packages/core/hardhat.config.js index de9b6e8fb..d67f0ef7e 100644 --- a/packages/core/hardhat.config.js +++ b/packages/core/hardhat.config.js @@ -40,7 +40,9 @@ module.exports = { ], overrides: { 'contracts/test/Namespaced.sol': { version: '0.8.20', settings }, - 'contracts/test/NamespacedConflicts.sol': { version: '0.8.20', settings }, + 'contracts/test/NamespacedConflictsStructsOnly.sol': { version: '0.8.20', settings }, + 'contracts/test/NamespacedConflictsWithVariables.sol': { version: '0.8.20', settings }, + 'contracts/test/NamespacedConflictsWithFunctions.sol': { version: '0.8.20', settings }, }, }, etherscan: { diff --git a/packages/core/src/storage-namespaced-conflicts-with-layout.test.ts b/packages/core/src/storage-namespaced-conflicts-with-layout.test.ts new file mode 100644 index 000000000..cecf343b2 --- /dev/null +++ b/packages/core/src/storage-namespaced-conflicts-with-layout.test.ts @@ -0,0 +1,95 @@ +import _test, { TestFn } from 'ava'; +import { ContractDefinition } from 'solidity-ast'; +import { findAll, astDereferencer } from 'solidity-ast/utils'; +import { artifacts } from 'hardhat'; + +import { SolcOutput } from './solc-api'; +import { StorageLayout } from './storage/layout'; +import { extractStorageLayout } from './storage/extract'; +import { solcInputOutputDecoder } from './src-decoder'; + +interface Context { + extractStorageLayout: (contract: string) => ReturnType; +} + +const test = _test as TestFn; + +test.before(async t => { + const origBuildInfo = await artifacts.getBuildInfo( + 'contracts/test/NamespacedConflictsWithFunctions.sol:DuplicateNamespace', + ); + const namespacedBuildInfo = await artifacts.getBuildInfo( + 'contracts/test/NamespacedConflictsWithVariables.sol:DuplicateNamespace', + ); + + if (origBuildInfo === undefined || namespacedBuildInfo === undefined) { + throw new Error('Build info not found'); + } + + const origSolcOutput: SolcOutput = origBuildInfo.output; + const origContracts: Record = {}; + const origStorageLayouts: Record = {}; + + const namespacedSolcOutput: SolcOutput = namespacedBuildInfo.output; + const namespacedContracts: Record = {}; + const namespacedStorageLayouts: Record = {}; + + const origContractDefs = []; + for (const def of findAll( + 'ContractDefinition', + origSolcOutput.sources['contracts/test/NamespacedConflictsWithFunctions.sol'].ast, + )) { + origContractDefs.push(def); + } + const namespacedContractDefs = []; + for (const def of findAll( + 'ContractDefinition', + namespacedSolcOutput.sources['contracts/test/NamespacedConflictsWithVariables.sol'].ast, + )) { + namespacedContractDefs.push(def); + } + + for (let i = 0; i < origContractDefs.length; i++) { + const origContractDef = origContractDefs[i]; + const namespacedContractDef = namespacedContractDefs[i]; + + origContracts[origContractDef.name] = origContractDef; + namespacedContracts[namespacedContractDef.name] = namespacedContractDef; + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + origStorageLayouts[origContractDef.name] = + origSolcOutput.contracts['contracts/test/NamespacedConflictsWithFunctions.sol'][ + origContractDef.name + ].storageLayout!; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + namespacedStorageLayouts[namespacedContractDef.name] = + namespacedSolcOutput.contracts['contracts/test/NamespacedConflictsWithVariables.sol'][ + namespacedContractDef.name + ].storageLayout!; + } + const origDeref = astDereferencer(origSolcOutput); + const namespacedDeref = astDereferencer(namespacedSolcOutput); + + const decodeSrc = solcInputOutputDecoder(origBuildInfo.input, origSolcOutput); + t.context.extractStorageLayout = name => + extractStorageLayout(origContracts[name], decodeSrc, origDeref, origStorageLayouts[name], { + deref: namespacedDeref, + contractDef: namespacedContracts[name], + storageLayout: origStorageLayouts[name], + }); +}); + +test('duplicate namespace', t => { + const error = t.throws(() => t.context.extractStorageLayout('DuplicateNamespace')); + t.snapshot(error?.message); +}); + +test('conflicts with parent', t => { + const error = t.throws(() => t.context.extractStorageLayout('ConflictsWithParent')); + t.snapshot(error?.message); +}); + +test('conflicts in both parents', t => { + const error = t.throws(() => t.context.extractStorageLayout('ConflictsInBothParents')); + t.snapshot(error?.message); +}); diff --git a/packages/core/src/storage-namespaced-conflicts-with-layout.test.ts.md b/packages/core/src/storage-namespaced-conflicts-with-layout.test.ts.md new file mode 100644 index 000000000..4c0bdfd4d --- /dev/null +++ b/packages/core/src/storage-namespaced-conflicts-with-layout.test.ts.md @@ -0,0 +1,43 @@ +# Snapshot report for `src/storage-namespaced-conflicts-with-layout.test.ts` + +The actual snapshot is saved in `storage-namespaced-conflicts-with-layout.test.ts.snap`. + +Generated by [AVA](https://avajs.dev). + +## duplicate namespace + +> Snapshot 1 + + `Namespace erc7201:conflicting is defined multiple times for contract DuplicateNamespace␊ + ␊ + The namespace erc7201:conflicting was found in structs at the following locations:␊ + - contracts/test/NamespacedConflictsWithFunctions.sol:10␊ + - contracts/test/NamespacedConflictsWithFunctions.sol:15␊ + ␊ + Use a unique namespace id for each struct annotated with '@custom:storage-location erc7201:' in your contract and its inherited contracts.` + +## conflicts with parent + +> Snapshot 1 + + `Namespace erc7201:conflicting is defined multiple times for contract ConflictsWithParent␊ + ␊ + The namespace erc7201:conflicting was found in structs at the following locations:␊ + - contracts/test/NamespacedConflictsWithFunctions.sol:30␊ + - contracts/test/NamespacedConflictsWithFunctions.sol:10␊ + - contracts/test/NamespacedConflictsWithFunctions.sol:15␊ + ␊ + Use a unique namespace id for each struct annotated with '@custom:storage-location erc7201:' in your contract and its inherited contracts.` + +## conflicts in both parents + +> Snapshot 1 + + `Namespace erc7201:conflicting is defined multiple times for contract ConflictsInBothParents␊ + ␊ + The namespace erc7201:conflicting was found in structs at the following locations:␊ + - contracts/test/NamespacedConflictsWithFunctions.sol:30␊ + - contracts/test/NamespacedConflictsWithFunctions.sol:10␊ + - contracts/test/NamespacedConflictsWithFunctions.sol:15␊ + ␊ + Use a unique namespace id for each struct annotated with '@custom:storage-location erc7201:' in your contract and its inherited contracts.` diff --git a/packages/core/src/storage-namespaced-conflicts-with-layout.test.ts.snap b/packages/core/src/storage-namespaced-conflicts-with-layout.test.ts.snap new file mode 100644 index 000000000..7f2d486cc Binary files /dev/null and b/packages/core/src/storage-namespaced-conflicts-with-layout.test.ts.snap differ diff --git a/packages/core/src/storage-namespaced-conflicts.test.ts b/packages/core/src/storage-namespaced-conflicts.test.ts index 44e7cb03b..5c90caa70 100644 --- a/packages/core/src/storage-namespaced-conflicts.test.ts +++ b/packages/core/src/storage-namespaced-conflicts.test.ts @@ -15,17 +15,23 @@ interface Context { const test = _test as TestFn; test.before(async t => { - const buildInfo = await artifacts.getBuildInfo('contracts/test/NamespacedConflicts.sol:DuplicateNamespace'); + const buildInfo = await artifacts.getBuildInfo( + 'contracts/test/NamespacedConflictsStructsOnly.sol:DuplicateNamespace', + ); if (buildInfo === undefined) { throw new Error('Build info not found'); } const solcOutput: SolcOutput = buildInfo.output; const contracts: Record = {}; const storageLayouts: Record = {}; - for (const def of findAll('ContractDefinition', solcOutput.sources['contracts/test/NamespacedConflicts.sol'].ast)) { + for (const def of findAll( + 'ContractDefinition', + solcOutput.sources['contracts/test/NamespacedConflictsStructsOnly.sol'].ast, + )) { contracts[def.name] = def; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - storageLayouts[def.name] = solcOutput.contracts['contracts/test/NamespacedConflicts.sol'][def.name].storageLayout!; + storageLayouts[def.name] = + solcOutput.contracts['contracts/test/NamespacedConflictsStructsOnly.sol'][def.name].storageLayout!; } const deref = astDereferencer(solcOutput); const decodeSrc = solcInputOutputDecoder(buildInfo.input, solcOutput); @@ -46,4 +52,4 @@ test('conflicts with parent', t => { test('conflicts in both parents', t => { const error = t.throws(() => t.context.extractStorageLayout('ConflictsInBothParents')); t.snapshot(error?.message); -}); \ No newline at end of file +}); diff --git a/packages/core/src/storage-namespaced-conflicts.test.ts.md b/packages/core/src/storage-namespaced-conflicts.test.ts.md index 965c2bd7c..18ea3c7b1 100644 --- a/packages/core/src/storage-namespaced-conflicts.test.ts.md +++ b/packages/core/src/storage-namespaced-conflicts.test.ts.md @@ -11,8 +11,8 @@ Generated by [AVA](https://avajs.dev). `Namespace erc7201:conflicting is defined multiple times for contract DuplicateNamespace␊ ␊ The namespace erc7201:conflicting was found in structs at the following locations:␊ - - contracts/test/NamespacedConflicts.sol:6␊ - - contracts/test/NamespacedConflicts.sol:11␊ + - contracts/test/NamespacedConflictsStructsOnly.sol:6␊ + - contracts/test/NamespacedConflictsStructsOnly.sol:11␊ ␊ Use a unique namespace id for each struct annotated with '@custom:storage-location erc7201:' in your contract and its inherited contracts.` @@ -23,9 +23,9 @@ Generated by [AVA](https://avajs.dev). `Namespace erc7201:conflicting is defined multiple times for contract ConflictsWithParent␊ ␊ The namespace erc7201:conflicting was found in structs at the following locations:␊ - - contracts/test/NamespacedConflicts.sol:18␊ - - contracts/test/NamespacedConflicts.sol:6␊ - - contracts/test/NamespacedConflicts.sol:11␊ + - contracts/test/NamespacedConflictsStructsOnly.sol:18␊ + - contracts/test/NamespacedConflictsStructsOnly.sol:6␊ + - contracts/test/NamespacedConflictsStructsOnly.sol:11␊ ␊ Use a unique namespace id for each struct annotated with '@custom:storage-location erc7201:' in your contract and its inherited contracts.` @@ -36,8 +36,8 @@ Generated by [AVA](https://avajs.dev). `Namespace erc7201:conflicting is defined multiple times for contract ConflictsInBothParents␊ ␊ The namespace erc7201:conflicting was found in structs at the following locations:␊ - - contracts/test/NamespacedConflicts.sol:18␊ - - contracts/test/NamespacedConflicts.sol:6␊ - - contracts/test/NamespacedConflicts.sol:11␊ + - contracts/test/NamespacedConflictsStructsOnly.sol:18␊ + - contracts/test/NamespacedConflictsStructsOnly.sol:6␊ + - contracts/test/NamespacedConflictsStructsOnly.sol:11␊ ␊ Use a unique namespace id for each struct annotated with '@custom:storage-location erc7201:' in your contract and its inherited contracts.` diff --git a/packages/core/src/storage-namespaced-conflicts.test.ts.snap b/packages/core/src/storage-namespaced-conflicts.test.ts.snap index 47cac122a..bd57c9822 100644 Binary files a/packages/core/src/storage-namespaced-conflicts.test.ts.snap and b/packages/core/src/storage-namespaced-conflicts.test.ts.snap differ diff --git a/packages/core/src/storage/namespace.ts b/packages/core/src/storage/namespace.ts index a7e678807..56c42acda 100644 --- a/packages/core/src/storage/namespace.ts +++ b/packages/core/src/storage/namespace.ts @@ -215,7 +215,9 @@ function getOriginalStruct(structCanonicalName: string, origContractDef: Contrac } } } - throw new Error(`Could not find original source location for namespace struct with name ${structCanonicalName} from contract ${origContractDef.name}`); + throw new Error( + `Could not find original source location for namespace struct with name ${structCanonicalName} from contract ${origContractDef.name}`, + ); } function getOriginalMemberSrc(structCanonicalName: string, memberLabel: string, origContractDef: ContractDefinition) {