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.
pull/5285/head
Kaan Uzdoğan 5 months ago committed by Aniket
parent a4d6eb3c48
commit eadfe0d4f1
  1. 111
      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?) => {
this.compilersArtefactsPerFile[file] = new CompilerAbstract(languageVersion, data, source, input)
getAllCompilerAbstracts() {
return this.compilersArtefactsPerFile
}
this.on('solidity', 'compilationFinished', (file, source, languageVersion, data, input, version) => {
onActivation() {
const saveCompilationResult = (file, source, languageVersion, data, input?) => {
this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source, input)
saveCompilationPerFileResult(file, source, languageVersion, data)
})
this.compilersArtefactsPerFile[file] = new CompilerAbstract(languageVersion, data, source, input)
}
this.on('vyper', 'compilationFinished', (file, source, languageVersion, data) => {
this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source)
saveCompilationPerFileResult(file, source, languageVersion, data)
})
this.on('solidity', '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('vyper', '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('lexon', '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('yulp', '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('solidityUnitTesting', '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('nahmii-compiler', '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('hardhat', '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('truffle', 'compilationFinished', saveCompilationResult)
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. <filename>:<contractname> 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
}
}

Loading…
Cancel
Save