From a148a7facf8455e763c1a4651b2fd61055bef2c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaan=20Uzdo=C4=9Fan?= Date: Fri, 14 Jun 2024 10:35:12 +0200 Subject: [PATCH 1/3] Add getAllCompilerAbstracts method to `compiler-artefacts.ts` The current compilerArtefacts plugin's existing methods had two flaws: 1. It wasn't possible to get all Compiler Abstracts available in one go. 2. It was possible to first get all files with `getAllContractsData` and then call each CompilerAbstract one by one with `getCompilerAbstract`, however, in that case the CompilerAbstract was missing the `input` because it wasn't passed to the contructor and saveCompilationPerFileResult. It was only done so for the `solidityUnitTesting` plugin listener. The compiler input is needed for consistent contract verification. While it's possible to generate a compiler input from the contract artefact, via the metadata, it is not always possible to get a match due to known bugs in compiler's AST generation in prev. versions. This results in different bytecode from the original compiler input's output vs the compilation output from the metadata file. --- .../src/lib/compiler-artefacts.ts | 111 +++++++----------- 1 file changed, 44 insertions(+), 67 deletions(-) diff --git a/libs/remix-core-plugin/src/lib/compiler-artefacts.ts b/libs/remix-core-plugin/src/lib/compiler-artefacts.ts index d008795aec4..90e8af79819 100644 --- a/libs/remix-core-plugin/src/lib/compiler-artefacts.ts +++ b/libs/remix-core-plugin/src/lib/compiler-artefacts.ts @@ -1,89 +1,67 @@ 'use strict' -import { Plugin } from '@remixproject/engine' -import { util } from '@remix-project/remix-lib' -import { CompilerAbstract } from '@remix-project/remix-solidity' +import {Plugin} from '@remixproject/engine' +import {util} from '@remix-project/remix-lib' +import {CompilerAbstract} from '@remix-project/remix-solidity' const profile = { name: 'compilerArtefacts', - methods: ['get', 'addResolvedContract', 'getCompilerAbstract', 'getAllContractDatas', 'getLastCompilationResult', 'getArtefactsByContractName', 'getContractDataFromAddress', 'getContractDataFromByteCode', 'saveCompilerAbstract'], + methods: ['get', 'addResolvedContract', 'getCompilerAbstract', 'getAllContractDatas', 'getLastCompilationResult', 'getArtefactsByContractName', 'getContractDataFromAddress', 'getContractDataFromByteCode', 'saveCompilerAbstract', 'getAllCompilerAbstracts'], events: [], - version: '0.0.1' + version: '0.0.1', } export class CompilerArtefacts extends Plugin { compilersArtefactsPerFile: any compilersArtefacts: any - constructor () { + constructor() { super(profile) this.compilersArtefacts = {} this.compilersArtefactsPerFile = {} } - clear () { + clear() { this.compilersArtefacts = {} this.compilersArtefactsPerFile = {} } - saveCompilerAbstract (file: string, compilerAbstract: CompilerAbstract) { + saveCompilerAbstract(file: string, compilerAbstract: CompilerAbstract) { this.compilersArtefactsPerFile[file] = compilerAbstract } - onActivation () { - const saveCompilationPerFileResult = (file, source, languageVersion, data, input?) => { + getAllCompilerAbstracts() { + return this.compilersArtefactsPerFile + } + + onActivation() { + const saveCompilationResult = (file, source, languageVersion, data, input?) => { + this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source, input) this.compilersArtefactsPerFile[file] = new CompilerAbstract(languageVersion, data, source, input) } - this.on('solidity', 'compilationFinished', (file, source, languageVersion, data, input, version) => { - this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source, input) - saveCompilationPerFileResult(file, source, languageVersion, data) - }) + this.on('solidity', 'compilationFinished', saveCompilationResult) - this.on('vyper', 'compilationFinished', (file, source, languageVersion, data) => { - this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source) - saveCompilationPerFileResult(file, source, languageVersion, data) - }) + this.on('vyper', 'compilationFinished', saveCompilationResult) - this.on('lexon', 'compilationFinished', (file, source, languageVersion, data) => { - this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source) - saveCompilationPerFileResult(file, source, languageVersion, data) - }) + this.on('lexon', 'compilationFinished', saveCompilationResult) - this.on('yulp', 'compilationFinished', (file, source, languageVersion, data) => { - this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source) - saveCompilationPerFileResult(file, source, languageVersion, data) - }) + this.on('yulp', 'compilationFinished', saveCompilationResult) - this.on('solidityUnitTesting', 'compilationFinished', (file, source, languageVersion, data, input, version) => { - this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source, input) - saveCompilationPerFileResult(file, source, languageVersion, data, input) - }) + this.on('solidityUnitTesting', 'compilationFinished', saveCompilationResult) - this.on('nahmii-compiler', 'compilationFinished', (file, source, languageVersion, data) => { - this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source) - saveCompilationPerFileResult(file, source, languageVersion, data) - }) + this.on('nahmii-compiler', 'compilationFinished', saveCompilationResult) - this.on('hardhat', 'compilationFinished', (file, source, languageVersion, data) => { - this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source) - saveCompilationPerFileResult(file, source, languageVersion, data) - }) + this.on('hardhat', 'compilationFinished', saveCompilationResult) - this.on('truffle', 'compilationFinished', (file, source, languageVersion, data) => { - this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source) - saveCompilationPerFileResult(file, source, languageVersion, data) - }) + this.on('truffle', 'compilationFinished', saveCompilationResult) - this.on('foundry', 'compilationFinished', (file, source, languageVersion, data) => { - this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source) - saveCompilationPerFileResult(file, source, languageVersion, data) - }) + this.on('foundry', 'compilationFinished', saveCompilationResult) } /** * Get artefacts for last compiled contract * * @returns last compiled contract compiler abstract */ - getLastCompilationResult () { + getLastCompilationResult() { return this.compilersArtefacts.__last } @@ -91,7 +69,7 @@ export class CompilerArtefacts extends Plugin { * Get compilation output for contracts compiled during a session of Remix IDE * @returns compilatin output */ - getAllContractDatas () { + getAllContractDatas() { return this.filterAllContractDatas(() => true) } @@ -99,7 +77,7 @@ export class CompilerArtefacts extends Plugin { * filter compilation output for contracts compiled during a session of Remix IDE * @returns compilatin output */ - filterAllContractDatas (filter) { + filterAllContractDatas(filter) { const contractsData = {} Object.keys(this.compilersArtefactsPerFile).map((targetFile) => { const artefact = this.compilersArtefactsPerFile[targetFile] @@ -124,7 +102,7 @@ export class CompilerArtefacts extends Plugin { * @param contractName contract name * @returns arefacts object, with fully qualified name (e.g; contracts/1_Storage.sol:Storage) as key */ - _getAllContractArtefactsfromOutput (compilerOutput, contractName) { + _getAllContractArtefactsfromOutput(compilerOutput, contractName) { const contractArtefacts = {} for (const filename in compilerOutput) { if (Object.keys(compilerOutput[filename]).includes(contractName)) contractArtefacts[filename + ':' + contractName] = compilerOutput[filename][contractName] @@ -139,12 +117,12 @@ export class CompilerArtefacts extends Plugin { * @param contractArtefacts populated resultant artefacts object, with fully qualified name (e.g: contracts/1_Storage.sol:Storage) as key * Once method execution completes, contractArtefacts object will hold all possible artefacts for contract */ - async _populateAllContractArtefactsFromFE (path, contractName, contractArtefacts) { + async _populateAllContractArtefactsFromFE(path, contractName, contractArtefacts) { const dirList = await this.call('fileManager', 'dirList', path) if (dirList && dirList.length) { for (const dirPath of dirList) { // check if directory contains an 'artifacts' folder and a 'build-info' folder inside 'artifacts' - if (dirPath === path + '/artifacts' && await this.call('fileManager', 'exists', dirPath + '/build-info')) { + if (dirPath === path + '/artifacts' && (await this.call('fileManager', 'exists', dirPath + '/build-info'))) { const buildFileList = await this.call('fileManager', 'fileList', dirPath + '/build-info') // process each build-info file to populate the artefacts for contractName for (const buildFile of buildFileList) { @@ -155,7 +133,7 @@ export class CompilerArtefacts extends Plugin { // populate the resultant object with artefacts Object.assign(contractArtefacts, artefacts) } - } else await this._populateAllContractArtefactsFromFE (dirPath, contractName, contractArtefacts) + } else await this._populateAllContractArtefactsFromFE(dirPath, contractName, contractArtefacts) } } else return } @@ -165,7 +143,7 @@ export class CompilerArtefacts extends Plugin { * @param name contract name or fully qualified name i.e. : e.g: contracts/1_Storage.sol:Storage * @returns artefacts for the contract */ - async getArtefactsByContractName (name) { + async getArtefactsByContractName(name) { const contractsDataByFilename = this.getAllContractDatas() // check if name is a fully qualified name if (name.includes(':')) { @@ -173,12 +151,11 @@ export class CompilerArtefacts extends Plugin { const nameArr = fullyQualifiedName.split(':') const filename = nameArr[0] const contract = nameArr[1] - if (Object.keys(contractsDataByFilename).includes(filename) && contractsDataByFilename[filename][contract]) - return contractsDataByFilename[filename][contract] + if (Object.keys(contractsDataByFilename).includes(filename) && contractsDataByFilename[filename][contract]) return contractsDataByFilename[filename][contract] else { const allContractsData = {} - await this._populateAllContractArtefactsFromFE ('contracts', contract, allContractsData) - if (allContractsData[fullyQualifiedName]) return { fullyQualifiedName, artefact: allContractsData[fullyQualifiedName] } + await this._populateAllContractArtefactsFromFE('contracts', contract, allContractsData) + if (allContractsData[fullyQualifiedName]) return {fullyQualifiedName, artefact: allContractsData[fullyQualifiedName]} else throw new Error(`Could not find artifacts for ${fullyQualifiedName}. Compile contract to generate artifacts.`) } } else { @@ -186,10 +163,10 @@ export class CompilerArtefacts extends Plugin { const contractArtefacts = this._getAllContractArtefactsfromOutput(contractsDataByFilename, contractName) let keys = Object.keys(contractArtefacts) if (!keys.length) { - await this._populateAllContractArtefactsFromFE ('contracts', contractName, contractArtefacts) + await this._populateAllContractArtefactsFromFE('contracts', contractName, contractArtefacts) keys = Object.keys(contractArtefacts) } - if (keys.length === 1) return { fullyQualifiedName: keys[0], artefact: contractArtefacts[keys[0]] } + if (keys.length === 1) return {fullyQualifiedName: keys[0], artefact: contractArtefacts[keys[0]]} else if (keys.length > 1) { throw new Error(`There are multiple artifacts for contract "${contractName}", please use a fully qualified name.\n Please replace ${contractName} for one of these options wherever you are trying to read its artifact: \n @@ -199,7 +176,7 @@ export class CompilerArtefacts extends Plugin { } } - async getCompilerAbstract (file) { + async getCompilerAbstract(file) { if (!file) return null if (this.compilersArtefactsPerFile[file]) return this.compilersArtefactsPerFile[file] const path = await this.call('fileManager', 'getPathFromUrl', file) @@ -215,30 +192,30 @@ export class CompilerArtefacts extends Plugin { return artefact } - addResolvedContract (address: string, compilerData: CompilerAbstract) { + addResolvedContract(address: string, compilerData: CompilerAbstract) { this.compilersArtefacts[address] = compilerData } - isResolved (address) { + isResolved(address) { return this.compilersArtefacts[address] !== undefined } - get (key) { + get(key) { return this.compilersArtefacts[key] } - async getContractDataFromAddress (address) { + async getContractDataFromAddress(address) { const code = await this.call('blockchain', 'getCode', address) return this.getContractDataFromByteCode(code) } - async getContractDataFromByteCode (code) { + async getContractDataFromByteCode(code) { let found this.filterAllContractDatas((file, contractsData) => { for (const name of Object.keys(contractsData)) { const contract = contractsData[name] if (util.compareByteCode(code, '0x' + contract.evm.deployedBytecode.object)) { - found = { name, contract, file } + found = {name, contract, file} return true } } From fcc027579a01320244d761f7d95dae4a981cfc8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaan=20Uzdo=C4=9Fan?= Date: Fri, 14 Jun 2024 10:44:28 +0200 Subject: [PATCH 2/3] Don't create two separate new CompilerAbstracts --- libs/remix-core-plugin/src/lib/compiler-artefacts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/remix-core-plugin/src/lib/compiler-artefacts.ts b/libs/remix-core-plugin/src/lib/compiler-artefacts.ts index 90e8af79819..428bc7bf751 100644 --- a/libs/remix-core-plugin/src/lib/compiler-artefacts.ts +++ b/libs/remix-core-plugin/src/lib/compiler-artefacts.ts @@ -34,8 +34,8 @@ export class CompilerArtefacts extends Plugin { onActivation() { const saveCompilationResult = (file, source, languageVersion, data, input?) => { - this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source, input) this.compilersArtefactsPerFile[file] = new CompilerAbstract(languageVersion, data, source, input) + this.compilersArtefacts.__last = this.compilersArtefactsPerFile[file] } this.on('solidity', 'compilationFinished', saveCompilationResult) From 2c4c42444ea912dcbf6f9a7a9408a30dcea73e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaan=20Uzdo=C4=9Fan?= Date: Mon, 17 Jun 2024 15:48:12 +0200 Subject: [PATCH 3/3] Add compilationSaved event --- libs/remix-core-plugin/src/lib/compiler-artefacts.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/remix-core-plugin/src/lib/compiler-artefacts.ts b/libs/remix-core-plugin/src/lib/compiler-artefacts.ts index 428bc7bf751..ded062af888 100644 --- a/libs/remix-core-plugin/src/lib/compiler-artefacts.ts +++ b/libs/remix-core-plugin/src/lib/compiler-artefacts.ts @@ -6,7 +6,7 @@ import {CompilerAbstract} from '@remix-project/remix-solidity' const profile = { name: 'compilerArtefacts', methods: ['get', 'addResolvedContract', 'getCompilerAbstract', 'getAllContractDatas', 'getLastCompilationResult', 'getArtefactsByContractName', 'getContractDataFromAddress', 'getContractDataFromByteCode', 'saveCompilerAbstract', 'getAllCompilerAbstracts'], - events: [], + events: ['compilationSaved'], version: '0.0.1', } @@ -36,6 +36,7 @@ export class CompilerArtefacts extends Plugin { const saveCompilationResult = (file, source, languageVersion, data, input?) => { this.compilersArtefactsPerFile[file] = new CompilerAbstract(languageVersion, data, source, input) this.compilersArtefacts.__last = this.compilersArtefactsPerFile[file] + this.emit('compilationSaved', {[file]: this.compilersArtefactsPerFile[file]}) } this.on('solidity', 'compilationFinished', saveCompilationResult)