aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts-gen/src/contracts-gen.ts
blob: 0160a8204635ce00478c7fe5789a2cdebf7c74b4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#!/usr/bin/env node

import { NameResolver } from '@0x/sol-resolver';
import { PackageJSON } from '@0x/types';
import { logUtils } from '@0x/utils';
import { CompilerOptions } from 'ethereum-types';
import * as fs from 'fs';
import * as _ from 'lodash';
import * as path from 'path';
import * as prettier from 'prettier';
import toSnakeCase = require('to-snake-case');

const SOLIDITY_EXTENSION = '.sol';
const DEFAULT_ARTIFACTS_DIR = 'artifacts';
const DEFAULT_CONTRACTS_DIR = 'contracts';
const DEFAULT_WRAPPERS_DIR = 'generated-wrappers';
const AUTO_GENERATED_BANNER = `/*
* -----------------------------------------------------------------------------
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
* -----------------------------------------------------------------------------
*/`;
const AUTO_GENERATED_BANNER_FOR_LISTS = `This list is auto-generated by contracts-gen. Don't edit manually.`;

(async () => {
    const packageDir = process.cwd();
    const compilerJSON = readJSONFile<CompilerOptions>('compiler.json');
    const contracts = compilerJSON.contracts;
    const contractsDir = compilerJSON.contractsDir || DEFAULT_CONTRACTS_DIR;
    const artifactsDir = compilerJSON.artifactsDir || DEFAULT_ARTIFACTS_DIR;
    const wrappersDir = DEFAULT_WRAPPERS_DIR;
    if (!_.isArray(contracts)) {
        throw new Error('Unable to run the generator bacause contracts key in compiler.json is not of type array');
    }
    const prettierConfig = await prettier.resolveConfig(packageDir);
    generateCompilerJSONContractsList(contracts, contractsDir, prettierConfig);
    generateArtifactsTs(contracts, artifactsDir, prettierConfig);
    generateWrappersTs(contracts, wrappersDir, prettierConfig);
    generateTsConfigJSONFilesList(contracts, artifactsDir, prettierConfig);
    generatePackageJSONABIConfig(contracts, artifactsDir, prettierConfig);
    process.exit(0);
})().catch(err => {
    logUtils.log(err);
    process.exit(1);
});

function generateCompilerJSONContractsList(
    contracts: string[],
    contractsDir: string,
    prettierConfig: prettier.Options | null,
): void {
    const COMPILER_JSON_FILE_PATH = 'compiler.json';
    const compilerJSON = readJSONFile<CompilerOptions>(COMPILER_JSON_FILE_PATH);
    compilerJSON.contracts = _.map(contracts, contract => {
        if (contract.endsWith(SOLIDITY_EXTENSION)) {
            // If it's already a relative path - NO-OP.
            return contract;
        } else {
            // If it's just a contract name - resolve it and rewrite.
            return new NameResolver(contractsDir).resolve(contract).path;
        }
    });
    compilerJSON.contracts = _.sortBy(compilerJSON.contracts);
    const compilerJSONString = JSON.stringify(compilerJSON);
    const formattedCompilerJSON = prettier.format(compilerJSONString, {
        ...prettierConfig,
        filepath: COMPILER_JSON_FILE_PATH,
    });
    fs.writeFileSync(COMPILER_JSON_FILE_PATH, formattedCompilerJSON);
}

function generateArtifactsTs(contracts: string[], artifactsDir: string, prettierConfig: prettier.Options | null): void {
    const imports = _.map(contracts, contract => {
        const contractName = path.basename(contract, SOLIDITY_EXTENSION);
        const importPath = path.join('..', artifactsDir, `${contractName}.json`);
        return `import * as ${contractName} from '${importPath}';`;
    });
    const sortedImports = _.sortBy(imports);
    const artifacts = _.map(contracts, contract => {
        const contractName = path.basename(contract, SOLIDITY_EXTENSION);
        if (contractName === 'ZRXToken') {
            // HACK(albrow): "as any" hack still required here because ZRXToken does not
            // conform to the v2 artifact type.
            return `${contractName}: (${contractName} as any) as ContractArtifact,`;
        } else {
            return `${contractName}: ${contractName} as ContractArtifact,`;
        }
    });
    const artifactsTs = `
    ${AUTO_GENERATED_BANNER}
    import { ContractArtifact } from 'ethereum-types';

    ${sortedImports.join('\n')}
    export const artifacts = {${artifacts.join('\n')}};
    `;
    const ARTIFACTS_TS_FILE_PATH = 'src/artifacts.ts';
    const formattedArtifactsTs = prettier.format(artifactsTs, { ...prettierConfig, filepath: ARTIFACTS_TS_FILE_PATH });
    fs.writeFileSync(ARTIFACTS_TS_FILE_PATH, formattedArtifactsTs);
}

function generateWrappersTs(contracts: string[], wrappersDir: string, prettierConfig: prettier.Options | null): void {
    const imports = _.map(contracts, contract => {
        const contractName = path.basename(contract, SOLIDITY_EXTENSION);
        const outputFileName = makeOutputFileName(contractName);
        const exportPath = path.join('..', wrappersDir, outputFileName);
        return `export * from '${exportPath}';`;
    });
    const sortedImports = _.sortBy(imports);
    const wrappersTs = `
    ${AUTO_GENERATED_BANNER}
    ${sortedImports.join('\n')}
    `;
    const WRAPPERS_TS_FILE_PATH = 'src/wrappers.ts';
    const formattedArtifactsTs = prettier.format(wrappersTs, { ...prettierConfig, filepath: WRAPPERS_TS_FILE_PATH });
    fs.writeFileSync(WRAPPERS_TS_FILE_PATH, formattedArtifactsTs);
}

function generateTsConfigJSONFilesList(
    contracts: string[],
    artifactsDir: string,
    prettierConfig: prettier.Options | null,
): void {
    const TS_CONFIG_FILE_PATH = 'tsconfig.json';
    const tsConfig = readJSONFile<any>(TS_CONFIG_FILE_PATH);
    tsConfig.files = _.map(contracts, contract => {
        const contractName = path.basename(contract, SOLIDITY_EXTENSION);
        const artifactPath = path.join(artifactsDir, `${contractName}.json`);
        return artifactPath;
    });
    tsConfig.files = _.sortBy(tsConfig.files);
    const tsConfigString = JSON.stringify(tsConfig);
    const formattedTsConfig = prettier.format(tsConfigString, { ...prettierConfig, filepath: TS_CONFIG_FILE_PATH });
    fs.writeFileSync(TS_CONFIG_FILE_PATH, formattedTsConfig);
}

function generatePackageJSONABIConfig(
    contracts: string[],
    artifactsDir: string,
    prettierConfig: prettier.Options | null,
): void {
    let packageJSON = readJSONFile<PackageJSON>('package.json');
    const contractNames = _.map(contracts, contract => {
        const contractName = path.basename(contract, SOLIDITY_EXTENSION);
        return contractName;
    });
    const sortedContractNames = _.sortBy(contractNames);
    packageJSON = {
        ...packageJSON,
        config: {
            ...packageJSON.config,
            'abis:comment': AUTO_GENERATED_BANNER_FOR_LISTS,
            abis: `${artifactsDir}/@(${sortedContractNames.join('|')}).json`,
        },
    };
    const PACKAGE_JSON_FILE_PATH = 'package.json';
    const packageJSONString = JSON.stringify(packageJSON);
    const formattedPackageJSON = prettier.format(packageJSONString, {
        ...prettierConfig,
        filepath: PACKAGE_JSON_FILE_PATH,
    });
    fs.writeFileSync(PACKAGE_JSON_FILE_PATH, formattedPackageJSON);
}

function makeOutputFileName(name: string): string {
    let fileName = toSnakeCase(name);
    // HACK: Snake case doesn't make a lot of sense for abbreviated names but we can't reliably detect abbreviations
    // so we special-case the abbreviations we use.
    fileName = fileName.replace('z_r_x', 'zrx').replace('e_r_c', 'erc');
    return fileName;
}

function readJSONFile<T>(filePath: string): T {
    const JSONString = fs.readFileSync(filePath, 'utf8');
    const parsed: T = JSON.parse(JSONString);
    return parsed;
}