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' 'use strict'
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { util } from '@remix-project/remix-lib' import {util} from '@remix-project/remix-lib'
import { CompilerAbstract } from '@remix-project/remix-solidity' import {CompilerAbstract} from '@remix-project/remix-solidity'
const profile = { const profile = {
name: 'compilerArtefacts', name: 'compilerArtefacts',
methods: ['get', 'addResolvedContract', 'getCompilerAbstract', 'getAllContractDatas', 'getLastCompilationResult', 'getArtefactsByContractName', 'getContractDataFromAddress', 'getContractDataFromByteCode', 'saveCompilerAbstract'], methods: ['get', 'addResolvedContract', 'getCompilerAbstract', 'getAllContractDatas', 'getLastCompilationResult', 'getArtefactsByContractName', 'getContractDataFromAddress', 'getContractDataFromByteCode', 'saveCompilerAbstract', 'getAllCompilerAbstracts'],
events: [], events: [],
version: '0.0.1' version: '0.0.1',
} }
export class CompilerArtefacts extends Plugin { export class CompilerArtefacts extends Plugin {
compilersArtefactsPerFile: any compilersArtefactsPerFile: any
compilersArtefacts: any compilersArtefacts: any
constructor () { constructor() {
super(profile) super(profile)
this.compilersArtefacts = {} this.compilersArtefacts = {}
this.compilersArtefactsPerFile = {} this.compilersArtefactsPerFile = {}
} }
clear () { clear() {
this.compilersArtefacts = {} this.compilersArtefacts = {}
this.compilersArtefactsPerFile = {} this.compilersArtefactsPerFile = {}
} }
saveCompilerAbstract (file: string, compilerAbstract: CompilerAbstract) { saveCompilerAbstract(file: string, compilerAbstract: CompilerAbstract) {
this.compilersArtefactsPerFile[file] = compilerAbstract this.compilersArtefactsPerFile[file] = compilerAbstract
} }
onActivation () { getAllCompilerAbstracts() {
const saveCompilationPerFileResult = (file, source, languageVersion, data, input?) => { return this.compilersArtefactsPerFile
this.compilersArtefactsPerFile[file] = new CompilerAbstract(languageVersion, data, source, input)
} }
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) 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.on('solidity', 'compilationFinished', saveCompilationResult)
this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source)
saveCompilationPerFileResult(file, source, languageVersion, data)
})
this.on('lexon', 'compilationFinished', (file, source, languageVersion, data) => { this.on('vyper', 'compilationFinished', saveCompilationResult)
this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source)
saveCompilationPerFileResult(file, source, languageVersion, data)
})
this.on('yulp', 'compilationFinished', (file, source, languageVersion, data) => { this.on('lexon', 'compilationFinished', saveCompilationResult)
this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source)
saveCompilationPerFileResult(file, source, languageVersion, data)
})
this.on('solidityUnitTesting', 'compilationFinished', (file, source, languageVersion, data, input, version) => { this.on('yulp', 'compilationFinished', saveCompilationResult)
this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source, input)
saveCompilationPerFileResult(file, source, languageVersion, data, input)
})
this.on('nahmii-compiler', 'compilationFinished', (file, source, languageVersion, data) => { this.on('solidityUnitTesting', 'compilationFinished', saveCompilationResult)
this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source)
saveCompilationPerFileResult(file, source, languageVersion, data)
})
this.on('hardhat', 'compilationFinished', (file, source, languageVersion, data) => { this.on('nahmii-compiler', 'compilationFinished', saveCompilationResult)
this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source)
saveCompilationPerFileResult(file, source, languageVersion, data)
})
this.on('truffle', 'compilationFinished', (file, source, languageVersion, data) => { this.on('hardhat', 'compilationFinished', saveCompilationResult)
this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source)
saveCompilationPerFileResult(file, source, languageVersion, data)
})
this.on('foundry', 'compilationFinished', (file, source, languageVersion, data) => { this.on('truffle', 'compilationFinished', saveCompilationResult)
this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source)
saveCompilationPerFileResult(file, source, languageVersion, data) this.on('foundry', 'compilationFinished', saveCompilationResult)
})
} }
/** /**
* Get artefacts for last compiled contract * Get artefacts for last compiled contract
* * @returns last compiled contract compiler abstract * * @returns last compiled contract compiler abstract
*/ */
getLastCompilationResult () { getLastCompilationResult() {
return this.compilersArtefacts.__last 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 * Get compilation output for contracts compiled during a session of Remix IDE
* @returns compilatin output * @returns compilatin output
*/ */
getAllContractDatas () { getAllContractDatas() {
return this.filterAllContractDatas(() => true) 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 * filter compilation output for contracts compiled during a session of Remix IDE
* @returns compilatin output * @returns compilatin output
*/ */
filterAllContractDatas (filter) { filterAllContractDatas(filter) {
const contractsData = {} const contractsData = {}
Object.keys(this.compilersArtefactsPerFile).map((targetFile) => { Object.keys(this.compilersArtefactsPerFile).map((targetFile) => {
const artefact = this.compilersArtefactsPerFile[targetFile] const artefact = this.compilersArtefactsPerFile[targetFile]
@ -124,7 +102,7 @@ export class CompilerArtefacts extends Plugin {
* @param contractName contract name * @param contractName contract name
* @returns arefacts object, with fully qualified name (e.g; contracts/1_Storage.sol:Storage) as key * @returns arefacts object, with fully qualified name (e.g; contracts/1_Storage.sol:Storage) as key
*/ */
_getAllContractArtefactsfromOutput (compilerOutput, contractName) { _getAllContractArtefactsfromOutput(compilerOutput, contractName) {
const contractArtefacts = {} const contractArtefacts = {}
for (const filename in compilerOutput) { for (const filename in compilerOutput) {
if (Object.keys(compilerOutput[filename]).includes(contractName)) contractArtefacts[filename + ':' + contractName] = compilerOutput[filename][contractName] 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 * @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 * 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) const dirList = await this.call('fileManager', 'dirList', path)
if (dirList && dirList.length) { if (dirList && dirList.length) {
for (const dirPath of dirList) { for (const dirPath of dirList) {
// check if directory contains an 'artifacts' folder and a 'build-info' folder inside 'artifacts' // 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') const buildFileList = await this.call('fileManager', 'fileList', dirPath + '/build-info')
// process each build-info file to populate the artefacts for contractName // process each build-info file to populate the artefacts for contractName
for (const buildFile of buildFileList) { for (const buildFile of buildFileList) {
@ -155,7 +133,7 @@ export class CompilerArtefacts extends Plugin {
// populate the resultant object with artefacts // populate the resultant object with artefacts
Object.assign(contractArtefacts, artefacts) Object.assign(contractArtefacts, artefacts)
} }
} else await this._populateAllContractArtefactsFromFE (dirPath, contractName, contractArtefacts) } else await this._populateAllContractArtefactsFromFE(dirPath, contractName, contractArtefacts)
} }
} else return } 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 * @param name contract name or fully qualified name i.e. <filename>:<contractname> e.g: contracts/1_Storage.sol:Storage
* @returns artefacts for the contract * @returns artefacts for the contract
*/ */
async getArtefactsByContractName (name) { async getArtefactsByContractName(name) {
const contractsDataByFilename = this.getAllContractDatas() const contractsDataByFilename = this.getAllContractDatas()
// check if name is a fully qualified name // check if name is a fully qualified name
if (name.includes(':')) { if (name.includes(':')) {
@ -173,12 +151,11 @@ export class CompilerArtefacts extends Plugin {
const nameArr = fullyQualifiedName.split(':') const nameArr = fullyQualifiedName.split(':')
const filename = nameArr[0] const filename = nameArr[0]
const contract = nameArr[1] const contract = nameArr[1]
if (Object.keys(contractsDataByFilename).includes(filename) && contractsDataByFilename[filename][contract]) if (Object.keys(contractsDataByFilename).includes(filename) && contractsDataByFilename[filename][contract]) return contractsDataByFilename[filename][contract]
return contractsDataByFilename[filename][contract]
else { else {
const allContractsData = {} const allContractsData = {}
await this._populateAllContractArtefactsFromFE ('contracts', contract, allContractsData) await this._populateAllContractArtefactsFromFE('contracts', contract, allContractsData)
if (allContractsData[fullyQualifiedName]) return { fullyQualifiedName, artefact: allContractsData[fullyQualifiedName] } if (allContractsData[fullyQualifiedName]) return {fullyQualifiedName, artefact: allContractsData[fullyQualifiedName]}
else throw new Error(`Could not find artifacts for ${fullyQualifiedName}. Compile contract to generate artifacts.`) else throw new Error(`Could not find artifacts for ${fullyQualifiedName}. Compile contract to generate artifacts.`)
} }
} else { } else {
@ -186,10 +163,10 @@ export class CompilerArtefacts extends Plugin {
const contractArtefacts = this._getAllContractArtefactsfromOutput(contractsDataByFilename, contractName) const contractArtefacts = this._getAllContractArtefactsfromOutput(contractsDataByFilename, contractName)
let keys = Object.keys(contractArtefacts) let keys = Object.keys(contractArtefacts)
if (!keys.length) { if (!keys.length) {
await this._populateAllContractArtefactsFromFE ('contracts', contractName, contractArtefacts) await this._populateAllContractArtefactsFromFE('contracts', contractName, contractArtefacts)
keys = Object.keys(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) { else if (keys.length > 1) {
throw new Error(`There are multiple artifacts for contract "${contractName}", please use a fully qualified name.\n 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 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 (!file) return null
if (this.compilersArtefactsPerFile[file]) return this.compilersArtefactsPerFile[file] if (this.compilersArtefactsPerFile[file]) return this.compilersArtefactsPerFile[file]
const path = await this.call('fileManager', 'getPathFromUrl', file) const path = await this.call('fileManager', 'getPathFromUrl', file)
@ -215,30 +192,30 @@ export class CompilerArtefacts extends Plugin {
return artefact return artefact
} }
addResolvedContract (address: string, compilerData: CompilerAbstract) { addResolvedContract(address: string, compilerData: CompilerAbstract) {
this.compilersArtefacts[address] = compilerData this.compilersArtefacts[address] = compilerData
} }
isResolved (address) { isResolved(address) {
return this.compilersArtefacts[address] !== undefined return this.compilersArtefacts[address] !== undefined
} }
get (key) { get(key) {
return this.compilersArtefacts[key] return this.compilersArtefacts[key]
} }
async getContractDataFromAddress (address) { async getContractDataFromAddress(address) {
const code = await this.call('blockchain', 'getCode', address) const code = await this.call('blockchain', 'getCode', address)
return this.getContractDataFromByteCode(code) return this.getContractDataFromByteCode(code)
} }
async getContractDataFromByteCode (code) { async getContractDataFromByteCode(code) {
let found let found
this.filterAllContractDatas((file, contractsData) => { this.filterAllContractDatas((file, contractsData) => {
for (const name of Object.keys(contractsData)) { for (const name of Object.keys(contractsData)) {
const contract = contractsData[name] const contract = contractsData[name]
if (util.compareByteCode(code, '0x' + contract.evm.deployedBytecode.object)) { if (util.compareByteCode(code, '0x' + contract.evm.deployedBytecode.object)) {
found = { name, contract, file } found = {name, contract, file}
return true return true
} }
} }

Loading…
Cancel
Save