-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #47 from decentraland/feature/dynamic-contracts
feat: Generate contract manifest from the ABIs. Trim artifacts on build.
- Loading branch information
Showing
25 changed files
with
1,191 additions
and
790 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import path from 'path' | ||
import { fsReadFilePromise } from './utils' | ||
|
||
export class Artifact { | ||
static PROPERTY_BLACKLIST = [ | ||
'bytecode', | ||
'sourceMap', | ||
'deployedSourceMap', | ||
'sourcePath', | ||
'ast', | ||
'compiler' | ||
] | ||
|
||
constructor(filePath) { | ||
this.path = filePath | ||
this.name = path.basename(filePath, path.extname(filePath)) | ||
} | ||
|
||
async trim() { | ||
let content = await fsReadFilePromise(this.path) | ||
content = JSON.parse(content) | ||
Artifact.PROPERTY_BLACKLIST.forEach(prop => delete content[prop]) | ||
return JSON.stringify(content, null, 2) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import path from 'path' | ||
import { Artifact } from './Artifact' | ||
import { findFolderPath, globPromise, fsWriteFilePromise } from './utils' | ||
|
||
const DEFAULT_FOLDER_PATH = 'src/contracts/artifacts' | ||
|
||
export class Artifacts { | ||
static PROPERTY_BLACKLIST = [ | ||
'bytecode', | ||
'sourceMap', | ||
'deployedSourceMap', | ||
'sourcePath', | ||
'ast', | ||
'compiler' | ||
] | ||
|
||
constructor(folderPath) { | ||
folderPath = folderPath || findFolderPath(DEFAULT_FOLDER_PATH) | ||
this.folderPath = folderPath | ||
this.collection = [] | ||
} | ||
|
||
async buildCollection() { | ||
const paths = await this.getPaths() | ||
this.collection = paths.map(path => new Artifact(path)) | ||
return this.collection | ||
} | ||
|
||
async getPaths() { | ||
const artifactsPattern = path.join(this.folderPath, 'MANAToken.json') | ||
return await globPromise(artifactsPattern) | ||
} | ||
|
||
async trim() { | ||
return await Promise.all(this.collection.map(artifact => artifact.trim())) | ||
} | ||
|
||
async write() { | ||
for (const artifact of this.collection) { | ||
const content = await artifact.trim() | ||
await fsWriteFilePromise(artifact.path, content) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import path from 'path' | ||
|
||
export class ContractFile { | ||
constructor(filePath) { | ||
this.path = filePath | ||
this.name = path.basename(filePath, path.extname(filePath)) | ||
this.abi = this.getAbi() | ||
} | ||
|
||
isIndex() { | ||
return this.name === 'index' | ||
} | ||
|
||
getAbi() { | ||
if (this.isIndex()) return | ||
|
||
try { | ||
const Contract = require(this.path)[this.name] | ||
return Contract.getDefaultAbi() | ||
} catch (error) { | ||
console.log( | ||
`Could not find an artifact for "${this.path}"`, | ||
error.message | ||
) | ||
} | ||
} | ||
|
||
getExtensions() { | ||
const extensions = {} | ||
|
||
for (const method of this.abi) { | ||
const { name, inputs, stateMutability, type } = method | ||
|
||
switch (type) { | ||
case 'function': { | ||
const args = [ | ||
...inputs.map((input, index) => input.name || `input${index}`), | ||
'...args' | ||
].join(', ') | ||
|
||
if (stateMutability === 'view') { | ||
extensions[name] = `function(${args}) { | ||
return this.call('${name}', ${args}) | ||
}` | ||
} else if (stateMutability === 'nonpayable') { | ||
extensions[name] = `function(${args}) { | ||
return this.transaction('${name}', ${args}) | ||
}` | ||
} | ||
break | ||
} | ||
default: | ||
break | ||
} | ||
} | ||
|
||
return extensions | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import fs from 'fs' | ||
import prettier from 'prettier' | ||
|
||
import { walkUp } from './utils' | ||
|
||
export class Formatter { | ||
format(text) { | ||
return prettier.format(text, this.getPrettierOptions()) | ||
} | ||
|
||
getPrettierOptions() { | ||
let eslintRules = null | ||
|
||
walkUp('.eslintrc', function(eslintrcPath) { | ||
if (fs.existsSync(eslintrcPath)) { | ||
const eslintrcText = fs.readFileSync(eslintrcPath, 'utf8') | ||
eslintRules = JSON.parse(eslintrcText).rules | ||
|
||
return true | ||
} | ||
}) | ||
|
||
return eslintRules ? eslintRules['prettier/prettier'][1] : {} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { ContractFile } from './ContractFile' | ||
|
||
export class IndexFile { | ||
constructor(contractPaths) { | ||
this.contractPaths = contractPaths | ||
this.contracts = this.instantiateContracts() | ||
} | ||
|
||
instantiateContracts() { | ||
return this.contractPaths | ||
.map(path => new ContractFile(path)) | ||
.filter(contract => !!contract.abi) | ||
} | ||
|
||
build() { | ||
return ` | ||
// This is auto-generated code | ||
// Changes here will be lost | ||
${this.buildImports()} | ||
${this.buildDefinitions()} | ||
${this.buildExports()}` | ||
} | ||
|
||
buildImports() { | ||
return this.contracts | ||
.map(contract => `import { ${contract.name} } from './${contract.name}'`) | ||
.join('\n') | ||
} | ||
|
||
buildDefinitions() { | ||
return this.contracts | ||
.map(contract => { | ||
const extensions = contract.getExtensions() | ||
const extensionObject = Object.keys(extensions) | ||
.map(name => `${name}: ${extensions[name]}`) | ||
.join(',\n\t\t') | ||
|
||
return `Object.assign( | ||
${contract.name}.prototype, { | ||
${extensionObject} | ||
}, | ||
${contract.name}.prototype | ||
)` | ||
}) | ||
.join('\n\n') | ||
} | ||
|
||
buildExports() { | ||
return `export { | ||
${this.contracts.map(contract => contract.name)} | ||
}` | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import path from 'path' | ||
import { findFolderPath, globPromise, fsWriteFilePromise } from './utils' | ||
|
||
const DEFAULT_FOLDER_PATH = 'src/contracts' | ||
|
||
export class Manifest { | ||
constructor(folderPath) { | ||
folderPath = folderPath || findFolderPath(DEFAULT_FOLDER_PATH) | ||
this.folderPath = folderPath | ||
} | ||
|
||
getPaths() { | ||
const contractsPattern = path.join(this.folderPath, '*.js') | ||
return globPromise(contractsPattern) | ||
} | ||
|
||
write(text = '') { | ||
const manifestPath = path.join(this.folderPath, 'index.js') | ||
return fsWriteFilePromise(manifestPath, text) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
#!/usr/bin/env babel-node | ||
|
||
import program from 'commander' | ||
|
||
import { Manifest } from './Manifest' | ||
import { Artifacts } from './Artifacts' | ||
import { IndexFile } from './IndexFile' | ||
import { Formatter } from './Formatter' | ||
|
||
export function main() { | ||
program | ||
.command('generate-manifest') | ||
.option( | ||
'--folderPath [folderPath]', | ||
'Folder containing the contract files. By default it will try to find the nearest src/contracts folder' | ||
) | ||
.option( | ||
'--write', | ||
'Whether write the file or just print it. Defaults to false', | ||
false | ||
) | ||
.action(async options => { | ||
const manifest = new Manifest(options.folderPath) | ||
const indexFile = await generateIndex(manifest) | ||
|
||
if (options.write) { | ||
await manifest.write(indexFile) | ||
console.log(`index file written on "${manifest.folderPath}"`) | ||
} else { | ||
console.log(indexFile) | ||
} | ||
}) | ||
|
||
program | ||
.command('trim-artifacts') | ||
.option( | ||
'--folderPath [folderPath]', | ||
'Folder containing the contract files. By default it will try to find the nearest src/contracts folder' | ||
) | ||
.option( | ||
'--write', | ||
'Whether write the file or just print it. Defaults to false', | ||
false | ||
) | ||
.action(async options => { | ||
const artifacts = new Artifacts(options.folderPath) | ||
await artifacts.buildCollection() | ||
|
||
console.log(`Trimming artifacts on ${artifacts.folderPath}`) | ||
|
||
if (options.write) { | ||
await artifacts.write() | ||
console.log(`artifacts written on "${artifacts.folderPath}"`) | ||
} else { | ||
const content = await artifacts.trim() | ||
console.log(content) | ||
} | ||
}) | ||
|
||
program.parse(process.argv) | ||
} | ||
|
||
async function generateIndex(manifest) { | ||
console.log(`Geneating manifest for "${manifest.folderPath}"`) | ||
|
||
const contractPaths = await manifest.getPaths() | ||
|
||
console.log(`Found ${contractPaths.length} contracts`) | ||
console.log('Building index file') | ||
|
||
const index = new IndexFile(contractPaths) | ||
const indexFile = index.build() | ||
|
||
console.log('Index built successufully') | ||
|
||
return new Formatter().format(indexFile) | ||
} | ||
|
||
if (require.main === module) { | ||
main() | ||
} |
Oops, something went wrong.