diff --git a/packages/hardhat-zksync-solc/package.json b/packages/hardhat-zksync-solc/package.json index b9d3ef52c..f3301ab06 100644 --- a/packages/hardhat-zksync-solc/package.json +++ b/packages/hardhat-zksync-solc/package.json @@ -22,7 +22,7 @@ "fmt": "yarn prettier --write", "eslint": "eslint 'src/**/*.ts' 'test/**/*.ts'", "prettier": "prettier 'src/**/*.ts' 'test/**/*.ts'", - "test": "c8 mocha test/tests.ts --no-timeout --exit", + "test": "c8 mocha --recursive \"test/tests/**/*.ts\" --exit", "build": "tsc --build .", "clean": "rimraf dist" }, @@ -61,7 +61,9 @@ "rimraf": "^3.0.2", "ts-node": "^10.6.0", "typescript": "^5.1.6", - "c8": "^8.0.1" + "c8": "^8.0.1", + "sinon": "^16.0.0", + "sinon-chai": "^3.7.0" }, "peerDependencies": { "hardhat": "^2.19.2" diff --git a/packages/hardhat-zksync-solc/test/compiler-files/linux/solc b/packages/hardhat-zksync-solc/test/compiler-files/linux/solc new file mode 100755 index 000000000..3d32e2cb8 Binary files /dev/null and b/packages/hardhat-zksync-solc/test/compiler-files/linux/solc differ diff --git a/packages/hardhat-zksync-solc/test/compiler-files/linux/zksolc b/packages/hardhat-zksync-solc/test/compiler-files/linux/zksolc new file mode 100755 index 000000000..82d9bbfa0 Binary files /dev/null and b/packages/hardhat-zksync-solc/test/compiler-files/linux/zksolc differ diff --git a/packages/hardhat-zksync-solc/test/compiler-files/macos/solc b/packages/hardhat-zksync-solc/test/compiler-files/macos/solc new file mode 100755 index 000000000..a7fe2d216 Binary files /dev/null and b/packages/hardhat-zksync-solc/test/compiler-files/macos/solc differ diff --git a/packages/hardhat-zksync-solc/test/compiler-files/macos/solc-macos:Zone.Identifier b/packages/hardhat-zksync-solc/test/compiler-files/macos/solc-macos:Zone.Identifier new file mode 100644 index 000000000..86e5542db --- /dev/null +++ b/packages/hardhat-zksync-solc/test/compiler-files/macos/solc-macos:Zone.Identifier @@ -0,0 +1,4 @@ +[ZoneTransfer] +ZoneId=3 +ReferrerUrl=https://github.com/ethereum/solidity/releases +HostUrl=https://objects.githubusercontent.com/github-production-release-asset-2e65be/40892817/d8c0c93a-8522-4772-be33-c8cb04a26d5d?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20231225%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20231225T225829Z&X-Amz-Expires=300&X-Amz-Signature=61e0d8865c9f98cd83643330f76ef3bb17b565f0d77aa45da8f5ca438d673973&X-Amz-SignedHeaders=host&actor_id=131957563&key_id=0&repo_id=40892817&response-content-disposition=attachment%3B%20filename%3Dsolc-macos&response-content-type=application%2Foctet-stream diff --git a/packages/hardhat-zksync-solc/test/compiler-files/macos/zksolc b/packages/hardhat-zksync-solc/test/compiler-files/macos/zksolc new file mode 100755 index 000000000..47f207713 Binary files /dev/null and b/packages/hardhat-zksync-solc/test/compiler-files/macos/zksolc differ diff --git a/packages/hardhat-zksync-solc/test/compiler-files/macos/zksolc-macosx-arm64-v1.3.17:Zone.Identifier b/packages/hardhat-zksync-solc/test/compiler-files/macos/zksolc-macosx-arm64-v1.3.17:Zone.Identifier new file mode 100644 index 000000000..f8abbf1f9 --- /dev/null +++ b/packages/hardhat-zksync-solc/test/compiler-files/macos/zksolc-macosx-arm64-v1.3.17:Zone.Identifier @@ -0,0 +1,4 @@ +[ZoneTransfer] +ZoneId=3 +ReferrerUrl=https://github.com/matter-labs/zksolc-bin/releases +HostUrl=https://objects.githubusercontent.com/github-production-release-asset-2e65be/423900857/a181a32b-51d2-443d-9c35-101bc5898a91?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20231225%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20231225T225424Z&X-Amz-Expires=300&X-Amz-Signature=b8068888dfde3a94f2627d1617a39d4117cedb8e47c1a93d087df2eb4b9af6f1&X-Amz-SignedHeaders=host&actor_id=131957563&key_id=0&repo_id=423900857&response-content-disposition=attachment%3B%20filename%3Dzksolc-macosx-arm64-v1.3.17&response-content-type=application%2Foctet-stream diff --git a/packages/hardhat-zksync-solc/test/fixture-projects/docker-compile/contracts/Greeter.sol b/packages/hardhat-zksync-solc/test/fixture-projects/docker-compile/contracts/Greeter.sol new file mode 100644 index 000000000..8045d4954 --- /dev/null +++ b/packages/hardhat-zksync-solc/test/fixture-projects/docker-compile/contracts/Greeter.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.4.22 <0.9.0; + +contract Greeter { + + string greeting; + string bad; + constructor(string memory _greeting) { + greeting = _greeting; + bad = "baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad"; + } + + function greet() public view returns (string memory) { + return greeting; + } + +} diff --git a/packages/hardhat-zksync-solc/test/fixture-projects/docker-compile/hardhat.config.ts b/packages/hardhat-zksync-solc/test/fixture-projects/docker-compile/hardhat.config.ts new file mode 100644 index 000000000..bb0225fb6 --- /dev/null +++ b/packages/hardhat-zksync-solc/test/fixture-projects/docker-compile/hardhat.config.ts @@ -0,0 +1,24 @@ +import '../../../src/index'; +import { HardhatUserConfig } from 'hardhat/config'; + +const config: HardhatUserConfig = { + zksolc: { + compilerSource: 'docker', + settings: { + experimental: { + dockerImage: "matterlabs/zksolc", + tag: "latest" + } + } + }, + networks: { + hardhat: { + zksync: true, + } + }, + solidity: { + version: '0.8.17' + }, +}; + +export default config; diff --git a/packages/hardhat-zksync-solc/test/fixture-projects/multiple-compilers/contracts/Greeter.sol b/packages/hardhat-zksync-solc/test/fixture-projects/multiple-compilers/contracts/Greeter.sol new file mode 100644 index 000000000..8045d4954 --- /dev/null +++ b/packages/hardhat-zksync-solc/test/fixture-projects/multiple-compilers/contracts/Greeter.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.4.22 <0.9.0; + +contract Greeter { + + string greeting; + string bad; + constructor(string memory _greeting) { + greeting = _greeting; + bad = "baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad"; + } + + function greet() public view returns (string memory) { + return greeting; + } + +} diff --git a/packages/hardhat-zksync-solc/test/fixture-projects/multiple-compilers/contracts/Greeter2.sol b/packages/hardhat-zksync-solc/test/fixture-projects/multiple-compilers/contracts/Greeter2.sol new file mode 100644 index 000000000..70ea81bca --- /dev/null +++ b/packages/hardhat-zksync-solc/test/fixture-projects/multiple-compilers/contracts/Greeter2.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.4.22 <0.9.0; + +contract Greeter2 { + + string greeting; + string bad; + constructor(string memory _greeting) { + greeting = _greeting; + bad = "baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad"; + } + + function greet() public view returns (string memory) { + return greeting; + } + +} diff --git a/packages/hardhat-zksync-solc/test/fixture-projects/multiple-compilers/hardhat.config.ts b/packages/hardhat-zksync-solc/test/fixture-projects/multiple-compilers/hardhat.config.ts new file mode 100644 index 000000000..20e1c5b8c --- /dev/null +++ b/packages/hardhat-zksync-solc/test/fixture-projects/multiple-compilers/hardhat.config.ts @@ -0,0 +1,27 @@ +import '../../../src/index'; +import { HardhatUserConfig } from 'hardhat/config'; + +const config: HardhatUserConfig = { + zksolc: { + compilerSource: 'binary', + }, + networks: { + hardhat: { + zksync: true, + } + }, + solidity: { + compilers: [ + { + version: '0.8.17' + } + ], + overrides: { + "contracts/Greeter2.sol": { + version: '0.8.16' + } + } + } +}; + +export default config; diff --git a/packages/hardhat-zksync-solc/test/fixture-projects/multiple-contracts/contracts/Greeter.sol b/packages/hardhat-zksync-solc/test/fixture-projects/multiple-contracts/contracts/Greeter.sol new file mode 100644 index 000000000..8045d4954 --- /dev/null +++ b/packages/hardhat-zksync-solc/test/fixture-projects/multiple-contracts/contracts/Greeter.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.4.22 <0.9.0; + +contract Greeter { + + string greeting; + string bad; + constructor(string memory _greeting) { + greeting = _greeting; + bad = "baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad"; + } + + function greet() public view returns (string memory) { + return greeting; + } + +} diff --git a/packages/hardhat-zksync-solc/test/fixture-projects/multiple-contracts/contracts/Greeter2.sol b/packages/hardhat-zksync-solc/test/fixture-projects/multiple-contracts/contracts/Greeter2.sol new file mode 100644 index 000000000..70ea81bca --- /dev/null +++ b/packages/hardhat-zksync-solc/test/fixture-projects/multiple-contracts/contracts/Greeter2.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.4.22 <0.9.0; + +contract Greeter2 { + + string greeting; + string bad; + constructor(string memory _greeting) { + greeting = _greeting; + bad = "baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad"; + } + + function greet() public view returns (string memory) { + return greeting; + } + +} diff --git a/packages/hardhat-zksync-solc/test/fixture-projects/multiple-contracts/hardhat.config.js b/packages/hardhat-zksync-solc/test/fixture-projects/multiple-contracts/hardhat.config.js new file mode 100644 index 000000000..12575417f --- /dev/null +++ b/packages/hardhat-zksync-solc/test/fixture-projects/multiple-contracts/hardhat.config.js @@ -0,0 +1 @@ +module.exports = require('../../common.config'); diff --git a/packages/hardhat-zksync-solc/test/fixture-projects/no-zksync/contracts/Greeter.sol b/packages/hardhat-zksync-solc/test/fixture-projects/no-zksync/contracts/Greeter.sol new file mode 100644 index 000000000..8045d4954 --- /dev/null +++ b/packages/hardhat-zksync-solc/test/fixture-projects/no-zksync/contracts/Greeter.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.4.22 <0.9.0; + +contract Greeter { + + string greeting; + string bad; + constructor(string memory _greeting) { + greeting = _greeting; + bad = "baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad"; + } + + function greet() public view returns (string memory) { + return greeting; + } + +} diff --git a/packages/hardhat-zksync-solc/test/fixture-projects/no-zksync/hardhat.config.ts b/packages/hardhat-zksync-solc/test/fixture-projects/no-zksync/hardhat.config.ts new file mode 100644 index 000000000..9e68131f8 --- /dev/null +++ b/packages/hardhat-zksync-solc/test/fixture-projects/no-zksync/hardhat.config.ts @@ -0,0 +1,18 @@ +import '../../../src/index'; +import { HardhatUserConfig } from 'hardhat/config'; + +const config: HardhatUserConfig = { + zksolc: { + compilerSource: 'binary', + }, + networks: { + hardhat: { + zksync: false, + } + }, + solidity: { + version: process.env.SOLC_VERSION || '0.8.17' + }, +}; + +export default config; diff --git a/packages/hardhat-zksync-solc/test/tests.ts b/packages/hardhat-zksync-solc/test/tests.ts deleted file mode 100644 index 47b68d110..000000000 --- a/packages/hardhat-zksync-solc/test/tests.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { assert } from 'chai'; -import { TASK_COMPILE } from 'hardhat/builtin-tasks/task-names'; -import { ZkSyncArtifact } from '../src/types'; -import chalk from 'chalk'; -import fs from 'fs'; - -import { useEnvironment } from './helpers'; - -describe('zksolc plugin', async function () { - describe('Simple', async function () { - useEnvironment('simple'); - - it('Should successfully compile a simple contract', async function () { - await this.env.run(TASK_COMPILE); - - const artifact = this.env.artifacts.readArtifactSync('Greeter') as ZkSyncArtifact; - - assert.equal(artifact.contractName, 'Greeter'); - - // Check that zkSync-specific artifact information was added. - assert.deepEqual(artifact.factoryDeps, {}, 'Contract unexpectedly has dependencies'); - }); - }); - - describe('Inlined library', async function () { - useEnvironment('library'); - - it('Should successfully compile the contract with inlined library', async function () { - if (this.env.config.solidity.compilers[0].version.startsWith('0.4')) { - console.info(chalk.cyan('Test skipped since is not applicable to Solidity 0.4.x.')); - return; - } - await this.env.run(TASK_COMPILE); - assert.equal(this.env.artifacts.readArtifactSync('contracts/Foo.sol:Foo').contractName, 'Foo'); - assert.equal(this.env.artifacts.readArtifactSync('contracts/Import.sol:Import').contractName, 'Import'); - }); - }); - - describe('Linked library', async function () { - useEnvironment('linked'); - - it('Should successfully compile the contract with linked library', async function () { - if (this.env.config.solidity.compilers[0].version.startsWith('0.4')) { - console.info(chalk.cyan('Test skipped since is not applicable to Solidity 0.4.x.')); - return; - } - await this.env.run(TASK_COMPILE); - assert.equal(this.env.artifacts.readArtifactSync('contracts/Foo.sol:Foo').contractName, 'Foo'); - assert.equal(this.env.artifacts.readArtifactSync('contracts/Import.sol:Import').contractName, 'Import'); - }); - }); - - describe('Factory', async function () { - useEnvironment('factory'); - - it('Should successfully compile the factory contract', async function () { - await this.env.run(TASK_COMPILE); - - const factoryArtifact = this.env.artifacts.readArtifactSync( - 'contracts/Factory.sol:Factory' - ) as ZkSyncArtifact; - const depArtifact = this.env.artifacts.readArtifactSync('contracts/Factory.sol:Dep') as ZkSyncArtifact; - - assert.equal(factoryArtifact.contractName, 'Factory'); - assert.equal(depArtifact.contractName, 'Dep'); - - // Check that zkSync-specific artifact information was added. - - // Factory contract should have one dependency. - // We do not check for the actual value of the hash, as it depends on the bytecode yielded by the compiler and thus not static. - // Instead we only check that it's a hash indeed. - const depHash = Object.keys(factoryArtifact.factoryDeps)[0]; - const expectedLength = 32 * 2 + 2; // 32 bytes in hex + '0x'. - assert(depHash.startsWith('0x') && depHash.length === expectedLength, 'Contract hash is malformed'); - - const depName = 'contracts/Factory.sol:Dep'; - assert.equal(depName, factoryArtifact.factoryDeps[depHash], 'No required dependency in the artifact'); - - // For the dependency contract should be no further dependencies. - assert.deepEqual(depArtifact.factoryDeps, {}, 'Unexpected factory-deps for a dependency contract'); - }); - }); - - describe('Nested Factory', async function () { - useEnvironment('nested'); - - it('Should successfully compile nested contracts', async function () { - await this.env.run(TASK_COMPILE); - - const factoryArtifact = this.env.artifacts.readArtifactSync('NestedFactory') as ZkSyncArtifact; - const fooDepArtifact = this.env.artifacts.readArtifactSync( - 'contracts/deps/Foo.sol:FooDep' - ) as ZkSyncArtifact; - const barDepArtifact = this.env.artifacts.readArtifactSync( - 'contracts/deps/more_deps/Bar.sol:BarDep' - ) as ZkSyncArtifact; - - // Check that zkSync-specific artifact information was added. - - // Factory contract should have one dependency. - // We do not check for the actual value of the hash, as it depends on the bytecode yielded by the compiler and thus not static. - // Instead we only check that it's a hash indeed. - const fooDepName = 'contracts/deps/Foo.sol:FooDep'; - const barDepName = 'contracts/deps/more_deps/Bar.sol:BarDep'; - for (const depName of [fooDepName, barDepName]) { - assert( - Object.values(factoryArtifact.factoryDeps).includes(depName), - `No required dependency in the artifact: ${depName}` - ); - } - for (const depHash in factoryArtifact.factoryDeps) { - const expectedLength = 32 * 2 + 2; // 32 bytes in hex + '0x'. - assert(depHash.startsWith('0x') && depHash.length === expectedLength, 'Contract hash is malformed'); - } - - // For the dependency contract should be no further dependencies. - for (const depArtifact of [fooDepArtifact, barDepArtifact]) { - assert.deepEqual(depArtifact.factoryDeps, {}, 'Unexpected factory-deps for a dependency contract'); - } - - // Each factory dependency should be accessible through `readArtifact` without changing it's identifier. - const fooDepArtifactFromFactoryDeps = this.env.artifacts.readArtifactSync(fooDepName); - assert.equal( - fooDepArtifactFromFactoryDeps.contractName, - fooDepArtifact.contractName, - 'Artifacts do not match' - ); - assert.equal(fooDepArtifactFromFactoryDeps.bytecode, fooDepArtifact.bytecode, 'Artifacts do not match'); - assert.deepEqual(fooDepArtifactFromFactoryDeps.abi, fooDepArtifact.abi, 'Artifacts do not match'); - }); - }); - - describe('Missing Library', async function () { - useEnvironment('missing-libraries'); - - it('Should successfully identify all the missing libraries', async function () { - if (this.env.config.solidity.compilers[0].version.startsWith('0.4')) { - console.info(chalk.cyan('Test skipped since is not applicable to Solidity 0.4.x.')); - return; - } - - await this.env.run(TASK_COMPILE); - - // Assert that there is a json file with the list of missing libraries at the location this.env.config.zksolc.settings.missingLibrariesPath. - const missingLibraries = JSON.parse(fs.readFileSync(this.env.config.zksolc.settings.missingLibrariesPath!, 'utf8')); - assert.isNotEmpty(missingLibraries); - - const expectedMissingLibraries = [ - { - "contractName": "ChildChildLib", - "contractPath": "contracts/ChildChildLib.sol", - "missingLibraries": [] - }, - { - "contractName": "ChildLib", - "contractPath": "contracts/ChildLib.sol", - "missingLibraries": [ - "contracts/ChildChildLib.sol:ChildChildLib" - ] - }, - { - "contractName": "MathLib", - "contractPath": "contracts/MathLib.sol", - "missingLibraries": [ - "contracts/ChildLib.sol:ChildLib" - ] - } - ]; - - // Assert that list of missing libraries is correct. - assert.deepEqual(missingLibraries, expectedMissingLibraries); - }); - - afterEach(async function () { - if (this.env.config.solidity.compilers[0].version.startsWith('0.4')) { - console.info(chalk.cyan('Test skipped since is not applicable to Solidity 0.4.x.')); - return; - } - - // Remove the file with the list of missing libraries. - fs.unlinkSync(this.env.config.zksolc.settings.missingLibrariesPath!); - }); - }); -}); diff --git a/packages/hardhat-zksync-solc/test/tests/compile/docker.test.ts b/packages/hardhat-zksync-solc/test/tests/compile/docker.test.ts new file mode 100644 index 000000000..8c1d3b09a --- /dev/null +++ b/packages/hardhat-zksync-solc/test/tests/compile/docker.test.ts @@ -0,0 +1,222 @@ +import { expect } from 'chai'; +import { + dockerImage, + validateDockerIsInstalled, + createDocker, + pullImageIfNecessary, + compileWithDocker, + getSolcVersion, +} from '../../../src/compile/docker'; +import { HardhatDocker } from '@nomiclabs/hardhat-docker'; +import { CompilerInput } from 'hardhat/types'; +import { ZkSolcConfig } from '../../../src/types'; +import sinon from 'sinon'; + +describe.skip('Docker', () => { + let docker: HardhatDocker; + + before(async () => { + await validateDockerIsInstalled(); + docker = await HardhatDocker.create(); + }); + + describe('dockerImage', () => { + it('should throw an error if no image name is specified', () => { + expect(() => dockerImage()).to.throw('Docker source was chosen but no image was specified'); + }); + + it('should return the correct image object', () => { + const imageName = 'matterlabs/zksolc'; + const imageTag = 'latest'; + const image = dockerImage(imageName, imageTag); + + expect(image.repository).to.equal(imageName); + expect(image.tag).to.equal(imageTag); + }); + }); + + describe('createDocker', () => { + it('should create a new HardhatDocker instance', async () => { + const hardhatDocker = await createDocker(); + + expect(hardhatDocker).to.be.an('object'); + expect(hardhatDocker).to.have.property('_docker'); + }); + }); + + describe('pullImageIfNecessary', () => { + describe('pullImageIfNecessaryInner', () => { + let hasPulledImageStub: sinon.SinonStub; + let pullImageStub: sinon.SinonStub; + let isImageUpToDateStub: sinon.SinonStub; + + + async function booleanPromise(bool: boolean): Promise { + return bool; + } + + afterEach(() => { + hasPulledImageStub.restore(); + pullImageStub.restore(); + isImageUpToDateStub.restore(); + }); + + + it('should pull the Docker image if it has not been pulled before', async () => { + hasPulledImageStub = sinon.stub(HardhatDocker.prototype, 'hasPulledImage').returns(booleanPromise(false)); + pullImageStub = sinon.stub(HardhatDocker.prototype, 'pullImage').resolves(); + isImageUpToDateStub = sinon.stub(HardhatDocker.prototype, 'isImageUpToDate').returns(booleanPromise(true)); + + const image = { repository: 'matterlabs/test', tag: 'latest' }; + + await pullImageIfNecessary(docker, image); + + expect(await docker.hasPulledImage(image)).to.be.false; + sinon.assert.calledOnce(pullImageStub); + }); + + it('should check for image updates if the Docker image has been pulled before', async () => { + hasPulledImageStub = sinon.stub(HardhatDocker.prototype, 'hasPulledImage').returns(booleanPromise(true)); + isImageUpToDateStub = sinon.stub(HardhatDocker.prototype, 'isImageUpToDate').returns(booleanPromise(true)); + pullImageStub = sinon.stub(HardhatDocker.prototype, 'pullImage').resolves(); + + const image = { repository: 'matterlabs/test', tag: 'latest' }; + + await pullImageIfNecessary(docker, image); + + expect(await docker.hasPulledImage(image)).to.be.true; + sinon.assert.calledOnce(isImageUpToDateStub); + sinon.assert.notCalled(pullImageStub); + }); + + it('should check for image updates if the Docker image has been pulled before with image not up to date', async () => { + hasPulledImageStub = sinon.stub(HardhatDocker.prototype, 'hasPulledImage').returns(booleanPromise(true)); + isImageUpToDateStub = sinon.stub(HardhatDocker.prototype, 'isImageUpToDate').returns(booleanPromise(false)); + pullImageStub = sinon.stub(HardhatDocker.prototype, 'pullImage').resolves(); + + const image = { repository: 'matterlabs/test', tag: 'latest' }; + + await pullImageIfNecessary(docker, image); + + expect(await docker.hasPulledImage(image)).to.be.true; + sinon.assert.calledOnce(isImageUpToDateStub); + sinon.assert.calledOnce(pullImageStub); + }); + }); + }); + + + describe('compileWithDocker', () => { + before(async () => { + const imageName = 'matterlabs/zksolc'; + const imageTag = 'latest'; + const image = dockerImage(imageName, imageTag); + + await pullImageIfNecessary(docker, image); + }); + + it('should compile the contract using Docker', async () => { + const imageName = 'matterlabs/zksolc'; + const imageTag = 'latest'; + const image = dockerImage(imageName, imageTag); + + const zksolcConfig: ZkSolcConfig = { + version: 'latest', + compilerSource: "docker", + settings: { + optimizer: { + enabled: false, + runs: 150 + }, + metadata: { + }, + experimental: { + dockerImage: imageName, + tag: imageTag + } + } + }; + + const input: CompilerInput = { + language: 'Solidity', + sources: { + 'contracts/Greeter.sol': { content: '// SPDX-License-Identifier: MIT\n\npragma solidity >=0.4.22 <0.9.0;\n\ncontract Greeter {\n\n string greeting;\n string bad;\n constructor(string memory _greeting) {\n greeting = _greeting;\n bad = "baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad";\n }\n\n function greet() public view returns (string memory) {\n return greeting;\n }\n\n}\n' } + }, + settings: { + outputSelection: { + '*': { + '*': ['abi', 'metadata'], + }, + }, + viaIR: false, + optimizer: { + enabled: true, + } + }, + }; + + const output = await compileWithDocker(input, docker, image, zksolcConfig); + output.contracts['contracts/Greeter.sol']['Greeter'].evm.bytecode.object; + expect(output).to.be.an('object'); + expect(output).to.have.property('contracts'); + expect(output.contracts).to.be.an('object'); + expect(output.contracts).to.have.property('contracts/Greeter.sol'); + expect(output.contracts['contracts/Greeter.sol']).to.be.an('object'); + expect(output.contracts['contracts/Greeter.sol']).to.have.property('Greeter'); + expect(output.contracts['contracts/Greeter.sol']['Greeter']).to.be.an('object'); + expect(output.contracts['contracts/Greeter.sol']['Greeter']).to.have.property('evm'); + expect(output.contracts['contracts/Greeter.sol']['Greeter']['evm']).to.be.an('object'); + expect(output.contracts['contracts/Greeter.sol']['Greeter']['evm']).to.have.property('bytecode'); + expect(output.contracts['contracts/Greeter.sol']['Greeter']['evm']['bytecode']).to.be.an('object'); + expect(output.contracts['contracts/Greeter.sol']['Greeter']['evm']['bytecode']).to.have.property('object'); + expect(output.contracts['contracts/Greeter.sol']['Greeter']['evm']['bytecode']['object']).to.be.a('string'); + + expect(output.errors).to.be.an('array'); + expect(output.version).eq('0.8.16'); + expect(output.zk_version).eq('1.1.6'); + }); + }); + + describe('getSolcVersion', () => { + it('should return the version of the solc compiler', async () => { + const imageName = 'matterlabs/zksolc'; + const imageTag = 'latest'; + const image = dockerImage(imageName, imageTag); + + const version = await getSolcVersion(docker, image); + expect(version).to.be.a('string'); + }); + }); + + describe('Docker', () => { + describe('validateDockerIsInstalled', () => { + it('should throw an error if Docker Desktop is not installed', async () => { + // Mock the isInstalled method to return false + HardhatDocker.isInstalled = async () => false; + try { + await validateDockerIsInstalled(); + // If the function does not throw an error, fail the test + expect.fail('Expected an error to be thrown'); + } catch (error: any) { + // Assert that the error message is correct + expect(error.message).to.eq( + 'Docker Desktop is not installed.\n' + + 'Please install it by following the instructions on https://www.docker.com/get-started' + ); + } + }); + + it('should not throw an error if Docker Desktop is installed', async () => { + // Mock the isInstalled method to return true + HardhatDocker.isInstalled = async () => true; + + try { + await validateDockerIsInstalled(); + } catch (error) { + // If the function throws an error, fail the test + expect.fail('Expected no error to be thrown'); + } + }); + }); + }); +}); diff --git a/packages/hardhat-zksync-solc/test/tests/compile/downloader.test.ts b/packages/hardhat-zksync-solc/test/tests/compile/downloader.test.ts new file mode 100644 index 000000000..3ed0a6f82 --- /dev/null +++ b/packages/hardhat-zksync-solc/test/tests/compile/downloader.test.ts @@ -0,0 +1,46 @@ +import { expect } from 'chai'; +import { ZksolcCompilerDownloader } from '../../../src/compile/downloader'; +import sinon from 'sinon'; + +describe('Downloader', async () => { + let sandbox = sinon.createSandbox(); + + async function isCompilerDownloaded(isZksolcDownloaded: boolean): Promise { + return isZksolcDownloaded; + } + + beforeEach(() => { + sandbox.stub(ZksolcCompilerDownloader.prototype, 'downloadCompiler').resolves(); + sandbox.stub(ZksolcCompilerDownloader.prototype, 'getVersion').returns('0.1.0'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + after(() => { + ((ZksolcCompilerDownloader as any)._instance) = undefined; + }); + + describe('getCompilerPath', async () => { + it('should return the configured compiler path if it exists', async () => { + sandbox.stub(ZksolcCompilerDownloader as any, '_getCompilerVersionInfo').resolves({latest: '0.1.0', minVersion: '0.0.1'}); + sandbox.stub(ZksolcCompilerDownloader.prototype, 'isCompilerDownloaded').returns(isCompilerDownloaded(false)); + sandbox.stub(ZksolcCompilerDownloader as any, '_shouldDownloadCompilerVersionInfo').resolves(false); + const downloader = await ZksolcCompilerDownloader.getDownloaderWithVersionValidated('0.1.0', 'path/zksolc', 'zksolc/0.1.0'); + const compilerPath = downloader.getCompilerPath(); + + expect(compilerPath).to.equal('path/zksolc'); + }); + + it('should return the default compiler path with salt if no configured compiler path', async () => { + sandbox.stub(ZksolcCompilerDownloader as any, '_getCompilerVersionInfo').resolves({latest: '0.1.0', minVersion: '0.0.1'}); + sandbox.stub(ZksolcCompilerDownloader.prototype, 'isCompilerDownloaded').returns(isCompilerDownloaded(false)); + sandbox.stub(ZksolcCompilerDownloader as any, '_shouldDownloadCompilerVersionInfo').resolves(false); + const downloader = await ZksolcCompilerDownloader.getDownloaderWithVersionValidated('0.1.0', 'path/zksolc', 'zksolc/0.1.0'); + const compilerPath = downloader.getCompilerPath(); + + expect(compilerPath).to.equal('path/zksolc'); + }); + }); +}); diff --git a/packages/hardhat-zksync-solc/test/tests/compile/index.test.ts b/packages/hardhat-zksync-solc/test/tests/compile/index.test.ts new file mode 100644 index 000000000..6e3ce432d --- /dev/null +++ b/packages/hardhat-zksync-solc/test/tests/compile/index.test.ts @@ -0,0 +1,89 @@ +import { expect } from 'chai'; +import { compile } from '../../../src/compile'; +import { ZkSolcConfig } from '../../../src/types'; +import { CompilerInput } from 'hardhat/types'; +import path from 'path'; + +describe('compile', () => { + + const input: CompilerInput = { + language: 'Solidity', + sources: { + 'contracts/Greeter.sol': { content: '// SPDX-License-Identifier: MIT\n\npragma solidity >=0.4.22 <0.9.0;\n\ncontract Greeter {\n\n string greeting;\n string bad;\n constructor(string memory _greeting) {\n greeting = _greeting;\n bad = "baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad";\n }\n\n function greet() public view returns (string memory) {\n return greeting;\n }\n\n}\n' } + }, + settings: { + outputSelection: { + '*': { + '*': ['abi', 'metadata'], + }, + }, + viaIR: false, + optimizer: { + enabled: true, + } + }, + }; + + it.skip('should compile with binary compiler', async () => { + let zksolcPath; + let solcPath; + + if (process.platform) { + zksolcPath = path.resolve('./test/compiler-files/linux/zksolc'); + solcPath = path.resolve('./test/compiler-files/linux/solc'); + } else { + zksolcPath = path.resolve('./test/compiler-files/macos/zksolc'); + solcPath = path.resolve('./test/compiler-files/macos/solc'); + } + + const zksolcConfig: ZkSolcConfig = { + compilerSource: 'binary', + version: '1.3.17', + settings: { + compilerPath: zksolcPath + } + }; + + let result = await compile(zksolcConfig, input, solcPath); + + expect(result).to.be.an('object'); + expect(result).to.have.property('errors'); + }); + + it('should compile with docker compiler', async () => { + const zksolcConfig: ZkSolcConfig = { + compilerSource: 'docker', + version: '1.3.17', + settings: { + experimental: { + dockerImage: 'matterlabs/zksolc', + tag: 'latest' + } + } + }; + + const result = await compile(zksolcConfig, input); + + expect(result).to.be.an('object'); + expect(result).to.have.property('errors'); + }); + + it('should throw an error for incorrect compiler source', async () => { + const zksolcConfig: ZkSolcConfig = { + compilerSource: undefined, + version: '', + settings: { + } + }; + + try { + await compile(zksolcConfig, input); + // If the function does not throw an error, fail the test + expect.fail('Expected ZkSyncSolcPluginError to be thrown'); + } catch (error: any) { + // Add your assertions here + // For example, you can check if the error message is as expected + expect(error.message).to.equal('Incorrect compiler source: undefined'); + } + }); +}); \ No newline at end of file diff --git a/packages/hardhat-zksync-solc/test/tests/error.test.ts b/packages/hardhat-zksync-solc/test/tests/error.test.ts new file mode 100644 index 000000000..07cf1fa99 --- /dev/null +++ b/packages/hardhat-zksync-solc/test/tests/error.test.ts @@ -0,0 +1,23 @@ +import { expect } from 'chai'; +import { ZkSyncSolcPluginError } from '../../src/errors'; + + describe('ZkSyncSolcPluginError', () => { + it('should create a new ZkSyncSolcPluginError instance', () => { + const message = 'Test error message'; + const parentError = new Error('Parent error'); + const error = new ZkSyncSolcPluginError(message, parentError); + + expect(error.name).to.equal("ZkSyncSolcPluginError"); + expect(error.message).to.equal(message); + expect(error.parent?.message).to.equal(parentError.message); + }); + + it('should have the correct stack trace', () => { + const message = 'Test error message'; + const error = new ZkSyncSolcPluginError(message); + + expect(error.stack).to.be.a('string'); + expect(error.stack).to.include('ZkSyncSolcPluginError'); + expect(error.stack).to.include(message); + }); + }); diff --git a/packages/hardhat-zksync-solc/test/tests/tests.ts b/packages/hardhat-zksync-solc/test/tests/tests.ts new file mode 100644 index 000000000..57eea94c2 --- /dev/null +++ b/packages/hardhat-zksync-solc/test/tests/tests.ts @@ -0,0 +1,468 @@ +import { assert } from 'chai'; +import { TASK_COMPILE, TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOBS, TASK_COMPILE_SOLIDITY_GET_DEPENDENCY_GRAPH, TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD, TASK_COMPILE_SOLIDITY_GET_SOURCE_NAMES, TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } from 'hardhat/builtin-tasks/task-names'; +import { ZkSyncArtifact } from '../../src/types'; +import chalk from 'chalk'; +import fs from 'fs'; +import sinonChai from 'sinon-chai'; +import chai from 'chai'; + +import { useEnvironment } from '../helpers'; +import { DependencyGraph } from 'hardhat/types/builtin-tasks/compile'; +import path from 'path'; +import { SOLIDITY_FILES_CACHE_FILENAME } from 'hardhat/internal/constants'; +import sinon from 'sinon'; +import { ZksolcCompilerDownloader } from '../../src/compile/downloader'; +import { CompilerDownloader } from "hardhat/internal/solidity/compiler/downloader"; + +chai.use(sinonChai); + +describe('zksolc plugin', async function () { + + describe('Extend HRE', async function () { + useEnvironment('multiple-compilers'); + + it('Should extend environment', async function () { + assert.isDefined(this.env.config.zksolc); + assert.isDefined(this.env.config.zksolc.settings); + assert.isDefined(this.env.config.zksolc.settings.libraries); + assert.isDefined(this.env.config.zksolc.settings.missingLibrariesPath); + + assert.include(this.env.config.paths.artifacts, '/fixture-projects/multiple-compilers/artifacts-zk'); + assert.include(this.env.config.paths.cache, '/fixture-projects/multiple-compilers/cache-zk'); + + let compilers = this.env.config.solidity.compilers; + + assert.equal(compilers.length, 1); + assert.equal(compilers[0].version, '0.8.17'); + assert.equal(compilers[0].settings.optimizer.enabled, true); + + let overrides = this.env.config.solidity.overrides; + assert.equal(overrides['contracts/Greeter2.sol'].version, '0.8.16'); + }); + }); + + describe('Filter contracts', async function () { + useEnvironment('multiple-contracts'); + + it('Should successfully return all contracts for compiling', async function () { + const rootPath = this.env.config.paths.root; + const sourcePaths: string[] = [rootPath + '/contracts/Greeter.sol', rootPath + '/contracts/Greeter2.sol']; + + const sourceNames: string[] = await this.env.run( + TASK_COMPILE_SOLIDITY_GET_SOURCE_NAMES, + { + rootPath, + sourcePaths, + } + ); + + assert.equal(2, sourceNames.length); + assert.equal('contracts/Greeter.sol', sourceNames[0]); + assert.equal('contracts/Greeter2.sol', sourceNames[1]); + + this.env.config.zksolc.settings.contractsToCompile = []; + + const sourceNames1: string[] = await this.env.run( + TASK_COMPILE_SOLIDITY_GET_SOURCE_NAMES, + { + rootPath, + sourcePaths, + } + ); + + assert.equal(2, sourceNames1.length); + assert.equal('contracts/Greeter.sol', sourceNames1[0]); + assert.equal('contracts/Greeter2.sol', sourceNames1[1]); + }); + + it('Should successfully return only Greeter contracts for compiling', async function () { + this.env.config.zksolc.settings.contractsToCompile = ['contracts/Greeter.sol']; + + const rootPath = this.env.config.paths.root; + const sourcePaths: string[] = [rootPath + '/contracts/Greeter.sol', rootPath + '/contracts/Greeter2.sol']; + + const sourceNames: string[] = await this.env.run( + TASK_COMPILE_SOLIDITY_GET_SOURCE_NAMES, + { + rootPath, + sourcePaths, + } + ); + + assert.equal(1, sourceNames.length); + assert.equal('contracts/Greeter.sol', sourceNames[0]); + }); + }); + + describe('Compilation jobs', async function () { + useEnvironment('multiple-contracts'); + + let sandbox = sinon.createSandbox(); + + let isZksolcDownloadedStub: sinon.SinonStub; + let getZksolcCompilerPathStub: sinon.SinonStub; + let getZksolcCompilerVersionStub: sinon.SinonStub; + let downloadCompilerStub: sinon.SinonStub; + + + async function isCompilerDownloaded(isZksolcDownloaded: boolean): Promise { + return isZksolcDownloaded; + } + + beforeEach(() => { + downloadCompilerStub = sandbox.stub(ZksolcCompilerDownloader.prototype, 'downloadCompiler').resolves(); + getZksolcCompilerPathStub = sandbox.stub(ZksolcCompilerDownloader.prototype, 'getCompilerPath').returns('zksolc/zksolc-version-0'); + getZksolcCompilerVersionStub = sandbox.stub(ZksolcCompilerDownloader.prototype, 'getVersion').returns('zksolc-version-0'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + after(() => { + ((ZksolcCompilerDownloader as any)._instance) = undefined; + }); + + it('Should download compiler and update jobs', async function () { + isZksolcDownloadedStub = sandbox.stub(ZksolcCompilerDownloader.prototype, 'isCompilerDownloaded').returns(isCompilerDownloaded(false)); + + const rootPath = this.env.config.paths.root; + const sourceNames: string[] = ['contracts/Greeter.sol', 'contracts/Greeter2.sol']; + + + const solidityFilesCachePath = path.join(this.env.config.paths.cache, SOLIDITY_FILES_CACHE_FILENAME); + + const dependencyGraph: DependencyGraph = await this.env.run( + TASK_COMPILE_SOLIDITY_GET_DEPENDENCY_GRAPH, + { rootPath, sourceNames, solidityFilesCachePath } + ); + + const { jobs, errors } = await this.env.run( + TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOBS, + { + dependencyGraph, + solidityFilesCachePath, + } + ); + + sandbox.assert.calledOnce(isZksolcDownloadedStub); + sandbox.assert.calledOnce(getZksolcCompilerPathStub); + sandbox.assert.calledOnce(getZksolcCompilerVersionStub); + sandbox.assert.calledOnceWithExactly(downloadCompilerStub); + + assert.equal(2, jobs.length); + assert.equal(0, errors.length); + + jobs.forEach((job: any) => { + const solidityConfig = job.solidityConfig; + assert.equal(solidityConfig.version, '0.8.17'); + assert.equal(solidityConfig.zksolc.version, 'zksolc-version-0'); + assert.equal(solidityConfig.zksolc.settings.compilerPath, "zksolc/zksolc-version-0"); + //assert.equal(solidityConfig.zksolc.settings.libraries, {}); + }); + }); + + it('Should not download compiler and update jobs with libraries', async function () { + isZksolcDownloadedStub = sandbox.stub(ZksolcCompilerDownloader.prototype, 'isCompilerDownloaded').returns(isCompilerDownloaded(true)); + + const rootPath = this.env.config.paths.root; + const sourceNames: string[] = ['contracts/Greeter.sol', 'contracts/Greeter2.sol']; + + this.env.config.zksolc.settings.libraries = { + 'contracts/Greeter.sol': { + 'contracts/Greeter.sol': "0x1234567890123456789012345678901234567890" + } + }; + + const solidityFilesCachePath = path.join(this.env.config.paths.cache, SOLIDITY_FILES_CACHE_FILENAME); + + const dependencyGraph: DependencyGraph = await this.env.run( + TASK_COMPILE_SOLIDITY_GET_DEPENDENCY_GRAPH, + { rootPath, sourceNames, solidityFilesCachePath } + ); + + const { jobs, errors } = await this.env.run( + TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOBS, + { + dependencyGraph, + solidityFilesCachePath, + } + ); + + sandbox.assert.calledOnce(isZksolcDownloadedStub); + sandbox.assert.calledOnce(getZksolcCompilerPathStub); + sandbox.assert.calledOnce(getZksolcCompilerVersionStub); + sandbox.assert.notCalled(downloadCompilerStub); + + assert.equal(2, jobs.length); + assert.equal(0, errors.length); + + jobs.forEach((job: any) => { + const solidityConfig = job.solidityConfig; + assert.equal(solidityConfig.version, '0.8.17'); + assert.equal(solidityConfig.zksolc.version, 'zksolc-version-0'); + assert.equal(solidityConfig.zksolc.settings.compilerPath, "zksolc/zksolc-version-0"); + assert.equal(solidityConfig.zksolc.settings.libraries['contracts/Greeter.sol']['contracts/Greeter.sol'], "0x1234567890123456789012345678901234567890"); + }); + }); + }); + + describe('Solc build', async function () { + describe('Solc build for docker', async function () { + useEnvironment('docker-compile'); + + it('Should get solc build for docker compiler', async function () { + + const build = await this.env.run( + TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD, + { + quiet: true, + solcVersion: '0.8.17', + } + ); + + assert.equal(build.compilerPath, ''); + assert.equal(build.isSolsJs, false); + assert.equal(build.version, '0.8.17'); + assert.equal(build.longVersion, ''); + + }); + }); + describe('Solc build for binary', async function () { + useEnvironment('multiple-contracts'); + let sandbox = sinon.createSandbox(); + + let isCompilerDownloadedStub: sinon.SinonStub; + let downloadCompilerStub: sinon.SinonStub; + + async function isCompilerDownloaded(): Promise { + return true; + } + + beforeEach(() => { + isCompilerDownloadedStub = sandbox.stub(CompilerDownloader.prototype, 'isCompilerDownloaded').returns(isCompilerDownloaded()); + downloadCompilerStub = sandbox.stub(CompilerDownloader.prototype, 'getCompiler').resolves({ compilerPath: 'solc/solc-version-0', version: '0.8.17', longVersion: 'solc/solc-version-0-long', isSolcJs: false }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + after(() => { + ((ZksolcCompilerDownloader as any)._instance) = undefined; + }); + + it('Should get solc build for binary compiler', async function () { + const build = await this.env.run( + TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD, + { + quiet: true, + solcVersion: '0.8.17', + } + ); + + assert.equal(build.compilerPath, 'solc/solc-version-0'); + assert.equal(build.isSolcJs, false); + assert.equal(build.version, '0.8.17'); + assert.equal(build.longVersion, 'solc/solc-version-0-long'); + }); + }); + }); + + describe('zksolc plugin compile without zksolc flag', async function () { + useEnvironment('no-zksync'); + + it('Should successfully compile a simple contract without zksync flag', async function () { + await this.env.run(TASK_COMPILE); + + const artifact = this.env.artifacts.readArtifactSync('Greeter') as ZkSyncArtifact; + + assert.equal(artifact.contractName, 'Greeter'); + + // Check that zkSync-specific artifact information was added. + assert.isUndefined(artifact.factoryDeps); + assert.isTrue(fs.existsSync(this.env.config.paths.cache)); + assert.isTrue(fs.existsSync(this.env.config.paths.artifacts)); + + assert.include(this.env.config.paths.artifacts, 'fixture-projects/no-zksync/artifacts') + assert.include(this.env.config.paths.cache, 'fixture-projects/no-zksync/cache') + }); + }); + + describe('zksolc plugin compile with zksolc flag', async function () { + describe('Simple', async function () { + useEnvironment('simple'); + + it('Should successfully compile a simple contract', async function () { + await this.env.run(TASK_COMPILE); + + const artifact = this.env.artifacts.readArtifactSync('Greeter') as ZkSyncArtifact; + + assert.equal(artifact.contractName, 'Greeter'); + + // Check that zkSync-specific artifact information was added. + assert.deepEqual(artifact.factoryDeps, {}, 'Contract unexpectedly has dependencies'); + }); + }); + + describe('Inlined library', async function () { + useEnvironment('library'); + + it('Should successfully compile the contract with inlined library', async function () { + if (this.env.config.solidity.compilers[0].version.startsWith('0.4')) { + console.info(chalk.cyan('Test skipped since is not applicable to Solidity 0.4.x.')); + return; + } + await this.env.run(TASK_COMPILE); + assert.equal(this.env.artifacts.readArtifactSync('contracts/Foo.sol:Foo').contractName, 'Foo'); + assert.equal(this.env.artifacts.readArtifactSync('contracts/Import.sol:Import').contractName, 'Import'); + }); + }); + + describe('Linked library', async function () { + useEnvironment('linked'); + + it('Should successfully compile the contract with linked library', async function () { + if (this.env.config.solidity.compilers[0].version.startsWith('0.4')) { + console.info(chalk.cyan('Test skipped since is not applicable to Solidity 0.4.x.')); + return; + } + await this.env.run(TASK_COMPILE); + assert.equal(this.env.artifacts.readArtifactSync('contracts/Foo.sol:Foo').contractName, 'Foo'); + assert.equal(this.env.artifacts.readArtifactSync('contracts/Import.sol:Import').contractName, 'Import'); + }); + }); + + describe('Factory', async function () { + useEnvironment('factory'); + + it('Should successfully compile the factory contract', async function () { + await this.env.run(TASK_COMPILE); + + const factoryArtifact = this.env.artifacts.readArtifactSync( + 'contracts/Factory.sol:Factory' + ) as ZkSyncArtifact; + const depArtifact = this.env.artifacts.readArtifactSync('contracts/Factory.sol:Dep') as ZkSyncArtifact; + + assert.equal(factoryArtifact.contractName, 'Factory'); + assert.equal(depArtifact.contractName, 'Dep'); + + // Check that zkSync-specific artifact information was added. + + // Factory contract should have one dependency. + // We do not check for the actual value of the hash, as it depends on the bytecode yielded by the compiler and thus not static. + // Instead we only check that it's a hash indeed. + const depHash = Object.keys(factoryArtifact.factoryDeps)[0]; + const expectedLength = 32 * 2 + 2; // 32 bytes in hex + '0x'. + assert(depHash.startsWith('0x') && depHash.length === expectedLength, 'Contract hash is malformed'); + + const depName = 'contracts/Factory.sol:Dep'; + assert.equal(depName, factoryArtifact.factoryDeps[depHash], 'No required dependency in the artifact'); + + // For the dependency contract should be no further dependencies. + assert.deepEqual(depArtifact.factoryDeps, {}, 'Unexpected factory-deps for a dependency contract'); + }); + }); + + describe('Nested Factory', async function () { + useEnvironment('nested'); + + it('Should successfully compile nested contracts', async function () { + await this.env.run(TASK_COMPILE); + + const factoryArtifact = this.env.artifacts.readArtifactSync('NestedFactory') as ZkSyncArtifact; + const fooDepArtifact = this.env.artifacts.readArtifactSync( + 'contracts/deps/Foo.sol:FooDep' + ) as ZkSyncArtifact; + const barDepArtifact = this.env.artifacts.readArtifactSync( + 'contracts/deps/more_deps/Bar.sol:BarDep' + ) as ZkSyncArtifact; + + // Check that zkSync-specific artifact information was added. + + // Factory contract should have one dependency. + // We do not check for the actual value of the hash, as it depends on the bytecode yielded by the compiler and thus not static. + // Instead we only check that it's a hash indeed. + const fooDepName = 'contracts/deps/Foo.sol:FooDep'; + const barDepName = 'contracts/deps/more_deps/Bar.sol:BarDep'; + for (const depName of [fooDepName, barDepName]) { + assert( + Object.values(factoryArtifact.factoryDeps).includes(depName), + `No required dependency in the artifact: ${depName}` + ); + } + for (const depHash in factoryArtifact.factoryDeps) { + const expectedLength = 32 * 2 + 2; // 32 bytes in hex + '0x'. + assert(depHash.startsWith('0x') && depHash.length === expectedLength, 'Contract hash is malformed'); + } + + // For the dependency contract should be no further dependencies. + for (const depArtifact of [fooDepArtifact, barDepArtifact]) { + assert.deepEqual(depArtifact.factoryDeps, {}, 'Unexpected factory-deps for a dependency contract'); + } + + // Each factory dependency should be accessible through `readArtifact` without changing it's identifier. + const fooDepArtifactFromFactoryDeps = this.env.artifacts.readArtifactSync(fooDepName); + assert.equal( + fooDepArtifactFromFactoryDeps.contractName, + fooDepArtifact.contractName, + 'Artifacts do not match' + ); + assert.equal(fooDepArtifactFromFactoryDeps.bytecode, fooDepArtifact.bytecode, 'Artifacts do not match'); + assert.deepEqual(fooDepArtifactFromFactoryDeps.abi, fooDepArtifact.abi, 'Artifacts do not match'); + }); + }); + + describe('Missing Library', async function () { + useEnvironment('missing-libraries'); + + it('Should successfully identify all the missing libraries', async function () { + if (this.env.config.solidity.compilers[0].version.startsWith('0.4')) { + console.info(chalk.cyan('Test skipped since is not applicable to Solidity 0.4.x.')); + return; + } + + await this.env.run(TASK_COMPILE); + + // Assert that there is a json file with the list of missing libraries at the location this.env.config.zksolc.settings.missingLibrariesPath. + const missingLibraries = JSON.parse(fs.readFileSync(this.env.config.zksolc.settings.missingLibrariesPath!, 'utf8')); + assert.isNotEmpty(missingLibraries); + + const expectedMissingLibraries = [ + { + "contractName": "ChildChildLib", + "contractPath": "contracts/ChildChildLib.sol", + "missingLibraries": [] + }, + { + "contractName": "ChildLib", + "contractPath": "contracts/ChildLib.sol", + "missingLibraries": [ + "contracts/ChildChildLib.sol:ChildChildLib" + ] + }, + { + "contractName": "MathLib", + "contractPath": "contracts/MathLib.sol", + "missingLibraries": [ + "contracts/ChildLib.sol:ChildLib" + ] + } + ]; + + // Assert that list of missing libraries is correct. + assert.deepEqual(missingLibraries, expectedMissingLibraries); + }); + + afterEach(async function () { + if (this.env.config.solidity.compilers[0].version.startsWith('0.4')) { + console.info(chalk.cyan('Test skipped since is not applicable to Solidity 0.4.x.')); + return; + } + + // Remove the file with the list of missing libraries. + fs.unlinkSync(this.env.config.zksolc.settings.missingLibrariesPath!); + }); + }); + }); +}); diff --git a/packages/hardhat-zksync-solc/test/tests/utils.test.ts b/packages/hardhat-zksync-solc/test/tests/utils.test.ts new file mode 100644 index 000000000..f91088ef4 --- /dev/null +++ b/packages/hardhat-zksync-solc/test/tests/utils.test.ts @@ -0,0 +1,261 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { + filterSupportedOutputSelections, + updateCompilerConf, + getVersionComponents, + writeLibrariesToFile, + download, + getLatestRelease, + saveDataToFile, + getZksolcUrl, + pluralize, + sha1 +} from '../../src/utils'; +import { CompilerOutputSelection, ZkSolcConfig } from '../../src/types'; +import { SolcConfig } from 'hardhat/types'; +import fs from "fs"; +import { MockAgent, setGlobalDispatcher } from 'undici' + + +describe('Utils', () => { + describe('filterSupportedOutputSelections', () => { + it('should filter unsupported output selections based on zkCompilerVersion', () => { + const outputSelection = { + 'file1.sol': { + 'Contract1': ['abi', 'evm.bytecode'] + }, + 'file2.sol': { + 'Contract2': ['abi', 'evm.bytecode', 'metadata'] + } + }; + const zkCompilerVersion = '1.3.7'; + + const filteredOutputSelection = filterSupportedOutputSelections(outputSelection, zkCompilerVersion); + + expect(filteredOutputSelection).to.deep.equal({ + 'file1.sol': { + 'Contract1': ['abi'] + }, + 'file2.sol': { + 'Contract2': ['abi', 'metadata'] + } + }); + }); + }); + + describe('updateCompilerConf', () => { + it('should update compiler configuration with zksolc settings', () => { + + const outputSelection: CompilerOutputSelection = { + 'file1.sol': { + 'Contract1': ['abi', 'evm.bytecode'] + }, + 'file2.sol': { + 'Contract2': ['abi', 'evm.bytecode', 'metadata'] + } + }; + + const compiler: SolcConfig = { + version: '0.8.17', + settings: { + optimizer: { + enabled: true, + runs: 200 + }, + outputSelection, + metadata: { + } + } + }; + const zksolc: ZkSolcConfig = { + version: '1.3.17', + settings: { + optimizer: { + enabled: false, + runs: 150 + }, + metadata: { + } + } + }; + + updateCompilerConf(compiler, zksolc); + + expect(compiler.settings.optimizer).to.deep.equal( + zksolc.settings.optimizer + ); + }); + }); + + describe('getVersionComponents', () => { + it('should return an array of version components', () => { + const version = '0.7.2'; + + const versionComponents = getVersionComponents(version); + + expect(versionComponents).to.deep.equal([0, 7, 2]); + }); + }); + + describe('writeLibrariesToFile', () => { + it('should write libraries to file', async () => { + const path = './test/libraries.json'; + const libraries = [ + { name: 'Library1', address: '0x1234567890' }, + { name: 'Library2', address: '0xabcdef1234' } + ]; + + await writeLibrariesToFile(path, libraries); + + expect(fs.existsSync(path)).to.be.true; + + fs.rmSync(path); + }); + }); + + describe('download', () => { + + const mockAgent = new MockAgent(); + setGlobalDispatcher(mockAgent); + + it('should download a file from a URL', async () => { + const url = 'https://example.com/'; + const filePath = './file.txt'; + const userAgent = 'Test User Agent'; + const version = '1.0.0'; + + const mockPool = mockAgent.get('https://example.com/'); + + mockPool.intercept({ + path: '/', + method: 'GET', + headers: { + }, + body: JSON.stringify({ + "User-Agent": `${userAgent} ${version}`, + }) + }).reply(200, { + message: 'all good' + }); + + await download(url, filePath, userAgent, version); + + expect(fs.existsSync(filePath)).is.true; + + fs.rmSync(filePath); + }); + }); + + describe('getRelease', () => { + const mockAgent = new MockAgent(); + setGlobalDispatcher(mockAgent); + + it('should get release information from GitHub', async () => { + const owner = 'example'; + const repo = 'project'; + const userAgent = 'Test User Agent'; + const tag = 'v1.0.0'; + + const mockPool = mockAgent.get('https://github.com'); + + mockPool.intercept({ + path: `/${owner}/${repo}/releases/latest`, + method: 'GET', + headers: { + "User-Agent": `${userAgent}`, + } + }).reply(302, { + }, { + headers: { + location: `https://github.com/${owner}/${repo}/releases/tag/${tag}` + }, + }); + + const release = await getLatestRelease(owner, repo, userAgent); + + expect(release).to.deep.equal('1.0.0'); + }); + }); + + describe('saveDataToFile', () => { + it('should save data to a file', async () => { + const testData = { name: 'Test Data' }; + const path = './test/libraries.json'; + + await saveDataToFile(testData, path); + + expect(fs.existsSync(path)).to.be.true; + + fs.rmSync(path); + }); + }); +}); + +describe('getZksolcUrl', () => { + + const platformStub = sinon.stub(process, 'platform'); + + it('should return the release URL when isRelease is true', () => { + platformStub.value('linux'); // Mock the process.platform property + const repo = 'example/repo'; + const version = '1.0.0'; + let expectedUrl = 'example/repo/releases/download/v1.0.0/zksolc-linux-amd64-musl-v1.0.0'; + + const url = getZksolcUrl(repo, version, true); + + expect(url).to.equal(expectedUrl); + + platformStub.value('darwin'); // Mock the process.platform property + expectedUrl = 'example/repo/releases/download/v1.0.0/zksolc-macosx-amd64-v1.0.0'; + + const urlMac = getZksolcUrl(repo, version, true); + + expect(urlMac).to.equal(expectedUrl); + }); + + it('should return the raw URL when isRelease is false', () => { + platformStub.value('linux'); // Mock the process.platform property + const repo = 'example/repo'; + const version = '1.0.0'; + let expectedUrl = 'example/repo/raw/main/linux-amd64/zksolc-linux-amd64-musl-v1.0.0'; + + const url = getZksolcUrl(repo, version, false); + + expect(url).to.equal(expectedUrl); + + platformStub.value('darwin'); // Mock the process.platform property + expectedUrl = 'example/repo/raw/main/macosx-amd64/zksolc-macosx-amd64-v1.0.0'; + + const urlMac = getZksolcUrl(repo, version, false); + + expect(urlMac).to.equal(expectedUrl); + }); +}); +describe('pluralize', () => { + it('should return singular when n is 1', () => { + const result = pluralize(1, 'apple', 'apples'); + expect(result).to.equal('apple'); + }); + + it('should return plural when n is not 1 and plural is provided', () => { + const result = pluralize(3, 'apple', 'apples'); + expect(result).to.equal('apples'); + }); + + it('should return singular with "s" appended when n is not 1 and plural is not provided', () => { + const result = pluralize(3, 'apple'); + expect(result).to.equal('apples'); + }); + + describe('sha1', () => { + it('should return the SHA1 hash of a string', () => { + const str = 'Hello, World!'; + const expectedHash = '0a0a9f2a6772942557ab5355d76af442f8f65e01'; + + const hash = sha1(str); + + expect(hash).to.equal(expectedHash); + }); + }); +}); \ No newline at end of file