@@ -1065,6 +1065,114 @@ contract CommentedImports is ERC20 {
10651065 ] )
10661066 } ,
10671067
1068+ 'Test complex local imports with external dependencies #group22' : function ( browser : NightwatchBrowser ) {
1069+ browser
1070+ // Create a realistic project structure with multiple folders and contracts
1071+ . addFile ( 'contracts/interfaces/IStorage.sol' , localImportsProjectSource [ 'contracts/interfaces/IStorage.sol' ] )
1072+ . addFile ( 'contracts/libraries/Math.sol' , localImportsProjectSource [ 'contracts/libraries/Math.sol' ] )
1073+ . addFile ( 'contracts/base/BaseContract.sol' , localImportsProjectSource [ 'contracts/base/BaseContract.sol' ] )
1074+ . addFile ( 'contracts/TokenVault.sol' , localImportsProjectSource [ 'contracts/TokenVault.sol' ] )
1075+ . addFile ( 'contracts/main/Staking.sol' , localImportsProjectSource [ 'contracts/main/Staking.sol' ] )
1076+ // Enable generate-contract-metadata to verify compilation artifacts
1077+ . waitForElementVisible ( '*[data-id="topbar-settingsIcon"]' )
1078+ . click ( '*[data-id="topbar-settingsIcon"]' )
1079+ . waitForElementVisible ( '*[data-id="settings-sidebar-general"]' )
1080+ . click ( '*[data-id="settings-sidebar-general"]' )
1081+ . waitForElementPresent ( '[data-id="generate-contract-metadataSwitch"]' )
1082+ . click ( '[data-id="generate-contract-metadataSwitch"]' )
1083+ // Open the main contract which imports everything
1084+ . openFile ( 'contracts/main/Staking.sol' )
1085+ // Switch to Solidity compiler panel
1086+ . clickLaunchIcon ( 'solidity' )
1087+ // Compile the contract
1088+ . click ( '[data-id="compilerContainerCompileBtn"]' )
1089+ . pause ( 5000 ) // Longer pause for multiple external imports
1090+ . clickLaunchIcon ( 'filePanel' )
1091+ // Verify external dependencies were resolved
1092+ . waitForElementVisible ( '*[data-id="treeViewDivDraggableItem.deps"]' , 60000 )
1093+ . click ( '*[data-id="treeViewDivDraggableItem.deps"]' )
1094+ . waitForElementVisible ( '*[data-id="treeViewDivDraggableItem.deps/npm"]' , 60000 )
1095+ . click ( '*[data-id="treeViewDivDraggableItem.deps/npm"]' )
1096+ . waitForElementVisible ( '*[data-id="treeViewDivDraggableItem.deps/npm/@openzeppelin"]' , 60000 )
1097+ . click ( '*[data-id="treeViewDivDraggableItem.deps/npm/@openzeppelin"]' )
1098+ . waitForElementVisible ( '*[data-id^="treeViewDivDraggableItem.deps/npm/@openzeppelin/contracts@"]' , 60000 )
1099+ . perform ( function ( ) {
1100+ browser . assert . ok ( true , 'External OpenZeppelin dependencies should be resolved' )
1101+ } )
1102+ // Verify compilation succeeded with mixed local and external imports
1103+ . waitForElementPresent ( '*[data-id="compiledContracts"]' , 10000 )
1104+ . perform ( function ( ) {
1105+ browser . assert . ok ( true , 'Complex project with local and external imports should compile successfully' )
1106+ } )
1107+ // Verify all local contracts are in the workspace (not in .deps)
1108+ . expandAllFolders ( )
1109+ . waitForElementVisible ( '*[data-id="treeViewDivDraggableItemcontracts/interfaces"]' , 10000 )
1110+ . waitForElementVisible ( '*[data-id="treeViewDivDraggableItemcontracts/libraries"]' , 10000 )
1111+ . waitForElementVisible ( '*[data-id="treeViewDivDraggableItemcontracts/base"]' , 10000 )
1112+ . waitForElementVisible ( '*[data-id="treeViewDivDraggableItemcontracts/main"]' , 10000 )
1113+ . waitForElementVisible ( '*[data-id="treeViewLitreeViewItemcontracts/TokenVault.sol"]' , 10000 )
1114+ . perform ( function ( ) {
1115+ browser . assert . ok ( true , 'All local contract folders should be present in workspace' )
1116+ } )
1117+ // Open resolution index to verify local imports are mapped correctly
1118+ . waitForElementVisible ( '*[data-id="treeViewLitreeViewItem.deps/npm/.resolution-index.json"]' , 60000 )
1119+ . openFile ( '.deps/npm/.resolution-index.json' )
1120+ . pause ( 1000 )
1121+ . getEditorValue ( ( content ) => {
1122+ try {
1123+ const idx = JSON . parse ( content )
1124+ const sourceFiles = Object . keys ( idx || { } )
1125+
1126+ // Find Staking.sol entry (main contract)
1127+ const stakingEntry = sourceFiles . find ( file => file . includes ( 'Staking.sol' ) )
1128+ browser . assert . ok ( ! ! stakingEntry , 'Resolution index should contain Staking.sol' )
1129+
1130+ if ( stakingEntry ) {
1131+ const mappings = idx [ stakingEntry ]
1132+ const mappingKeys = Object . keys ( mappings )
1133+
1134+ // Verify that local imports are NOT in the mappings (they should be direct)
1135+ const hasLocalImport = mappingKeys . some ( key =>
1136+ key . includes ( '../base/BaseContract.sol' ) ||
1137+ key . includes ( '../TokenVault.sol' )
1138+ )
1139+ browser . assert . ok ( ! hasLocalImport , 'Local relative imports should not be in resolution index' )
1140+
1141+ // Verify that external imports ARE in the mappings
1142+ const hasExternalImport = mappingKeys . some ( key =>
1143+ key . includes ( '@openzeppelin/contracts' )
1144+ )
1145+ browser . assert . ok ( hasExternalImport , 'External imports should be mapped in resolution index' )
1146+ }
1147+ } catch ( e ) {
1148+ browser . assert . fail ( 'Resolution index should be valid JSON: ' + e . message )
1149+ }
1150+ } )
1151+ // Verify build-info artifacts contain both local and external contracts
1152+ . verifyArtifactsBuildInfo ( [
1153+ {
1154+ packagePath : 'contracts/main/Staking.sol' ,
1155+ versionComment : 'SPDX-License-Identifier: MIT' ,
1156+ description : 'Should find local Staking.sol contract in build-info'
1157+ } ,
1158+ {
1159+ packagePath : 'contracts/base/BaseContract.sol' ,
1160+ versionComment : 'SPDX-License-Identifier: MIT' ,
1161+ description : 'Should find local BaseContract.sol in build-info'
1162+ } ,
1163+ {
1164+ packagePath : '@openzeppelin/contracts' ,
1165+ versionComment : 'Ownable.sol' ,
1166+ description : 'Should find external OpenZeppelin Ownable.sol in build-info'
1167+ } ,
1168+ {
1169+ packagePath : '@openzeppelin/contracts' ,
1170+ versionComment : 'Pausable.sol' ,
1171+ description : 'Should find external OpenZeppelin Pausable.sol in build-info'
1172+ }
1173+ ] )
1174+ } ,
1175+
10681176}
10691177
10701178// Named source objects for each test group
@@ -1735,6 +1843,164 @@ contract ChainlinkMultiVersion {
17351843 }
17361844}
17371845
1846+ const localImportsProjectSource = {
1847+ 'contracts/interfaces/IStorage.sol' : {
1848+ content : `// SPDX-License-Identifier: MIT
1849+ pragma solidity ^0.8.20;
1850+
1851+ /**
1852+ * @title IStorage
1853+ * @dev Interface for storage operations
1854+ */
1855+ interface IStorage {
1856+ function store(uint256 value) external;
1857+ function retrieve() external view returns (uint256);
1858+ }
1859+ `
1860+ } ,
1861+ 'contracts/libraries/Math.sol' : {
1862+ content : `// SPDX-License-Identifier: MIT
1863+ pragma solidity ^0.8.20;
1864+
1865+ /**
1866+ * @title Math
1867+ * @dev Basic math operations library
1868+ */
1869+ library Math {
1870+ function add(uint256 a, uint256 b) internal pure returns (uint256) {
1871+ return a + b;
1872+ }
1873+
1874+ function multiply(uint256 a, uint256 b) internal pure returns (uint256) {
1875+ return a * b;
1876+ }
1877+ }
1878+ `
1879+ } ,
1880+ 'contracts/base/BaseContract.sol' : {
1881+ content : `// SPDX-License-Identifier: MIT
1882+ pragma solidity ^0.8.20;
1883+
1884+ // Local import from interfaces folder
1885+ import "../interfaces/IStorage.sol";
1886+
1887+ // External import from OpenZeppelin
1888+ import "@openzeppelin/contracts/access/Ownable.sol";
1889+
1890+ /**
1891+ * @title BaseContract
1892+ * @dev Base contract with storage and access control
1893+ */
1894+ abstract contract BaseContract is IStorage, Ownable {
1895+ uint256 private storedValue;
1896+
1897+ constructor() Ownable(msg.sender) {}
1898+
1899+ function store(uint256 value) external override onlyOwner {
1900+ storedValue = value;
1901+ }
1902+
1903+ function retrieve() external view override returns (uint256) {
1904+ return storedValue;
1905+ }
1906+ }
1907+ `
1908+ } ,
1909+ 'contracts/TokenVault.sol' : {
1910+ content : `// SPDX-License-Identifier: MIT
1911+ pragma solidity ^0.8.20;
1912+
1913+ // Local import from libraries
1914+ import "./libraries/Math.sol";
1915+
1916+ // External imports from OpenZeppelin
1917+ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
1918+ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
1919+
1920+ /**
1921+ * @title TokenVault
1922+ * @dev Manages ERC20 token deposits
1923+ */
1924+ contract TokenVault {
1925+ using SafeERC20 for IERC20;
1926+ using Math for uint256;
1927+
1928+ mapping(address => mapping(address => uint256)) public deposits;
1929+
1930+ event Deposited(address indexed user, address indexed token, uint256 amount);
1931+
1932+ function deposit(address token, uint256 amount) external {
1933+ require(amount > 0, "Amount must be greater than 0");
1934+
1935+ IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
1936+ deposits[msg.sender][token] = Math.add(deposits[msg.sender][token], amount);
1937+
1938+ emit Deposited(msg.sender, token, amount);
1939+ }
1940+
1941+ function getDeposit(address user, address token) external view returns (uint256) {
1942+ return deposits[user][token];
1943+ }
1944+ }
1945+ `
1946+ } ,
1947+ 'contracts/main/Staking.sol' : {
1948+ content : `// SPDX-License-Identifier: MIT
1949+ pragma solidity ^0.8.20;
1950+
1951+ // Local imports - relative paths from different folders
1952+ import "../base/BaseContract.sol";
1953+ import "../TokenVault.sol";
1954+ import "../libraries/Math.sol";
1955+
1956+ // External imports from OpenZeppelin
1957+ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
1958+ import "@openzeppelin/contracts/utils/Pausable.sol";
1959+
1960+ /**
1961+ * @title Staking
1962+ * @dev Main staking contract that combines local and external dependencies
1963+ */
1964+ contract Staking is BaseContract, Pausable {
1965+ using Math for uint256;
1966+
1967+ TokenVault public vault;
1968+ IERC20 public stakingToken;
1969+
1970+ mapping(address => uint256) public stakedBalance;
1971+
1972+ event Staked(address indexed user, uint256 amount);
1973+
1974+ constructor(address _stakingToken, address _vault) {
1975+ stakingToken = IERC20(_stakingToken);
1976+ vault = TokenVault(_vault);
1977+ }
1978+
1979+ function stake(uint256 amount) external whenNotPaused {
1980+ require(amount > 0, "Cannot stake 0");
1981+
1982+ stakingToken.transferFrom(msg.sender, address(this), amount);
1983+ stakedBalance[msg.sender] = Math.add(stakedBalance[msg.sender], amount);
1984+
1985+ emit Staked(msg.sender, amount);
1986+ }
1987+
1988+ function getStakedBalance(address user) external view returns (uint256) {
1989+ return stakedBalance[user];
1990+ }
1991+
1992+ function pause() external onlyOwner {
1993+ _pause();
1994+ }
1995+
1996+ function unpause() external onlyOwner {
1997+ _unpause();
1998+ }
1999+ }
2000+ `
2001+ }
2002+ }
2003+
17382004// Keep sources array for backwards compatibility with @sources function
17392005const sources = [
17402006 upgradeableNFTSource ,
@@ -1761,5 +2027,6 @@ const sources = [
17612027 jsDelivrMultiVersionSource ,
17622028 jsDelivrV5WithV4UtilsSource ,
17632029 chainlinkMultiVersionSource ,
2030+ localImportsProjectSource ,
17642031]
17652032
0 commit comments