aboutsummaryrefslogblamecommitdiffstats
path: root/packages/deployer/src/utils/compiler.ts
blob: 600495693496359877ea11f042b82e81e8b5bcf8 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                                   






                                            
                                                                                     

   







































                                                                                                            
                                                                          
                                                       























                                                                                                

                                                    
   



                                                                                    








                                                                   
                                                                     



























                                                                               


                                                              
                                                      

                                  
                                                                                   









                                                                                                                                 

                                                                                             








                                                        
                                                     

                                                     

                                                                       






                                                                                 
                                
                                                                             





                                                 
import { AbiType, ContractAbi, MethodAbi } from '@0xproject/types';
import { logUtils } from '@0xproject/utils';
import * as _ from 'lodash';
import * as path from 'path';
import * as solc from 'solc';

import { constants } from './constants';
import { fsWrapper } from './fs_wrapper';
import { ContractArtifact, ContractSources, FunctionNameToSeenCount } from './types';

/**
 * Constructs a system-wide unique identifier for a source file.
 * @param directoryNamespace Namespace of the source file's root contract directory.
 * @param sourceFilePath Path to a source file, relative to contractBaseDir.
 * @return sourceFileId A system-wide unique identifier for the source file.
 */
export function constructUniqueSourceFileId(directoryNamespace: string, sourceFilePath: string): string {
    const namespacePrefix = !_.isEmpty(directoryNamespace) ? `/${directoryNamespace}` : '';
    const sourceFilePathNoLeadingSlash = sourceFilePath.replace(/^\/+/g, '');
    const sourceFileId = `${namespacePrefix}/${sourceFilePathNoLeadingSlash}`;
    return sourceFileId;
}
/**
 * Constructs a system-wide unique identifier for a dependency file.
 * @param dependencyFilePath Path from a sourceFile to a dependency.
 * @param  contractBaseDir Base contracts directory of search tree.
 * @return sourceFileId A system-wide unique identifier for the source file.
 */
export function constructDependencyFileId(dependencyFilePath: string, sourceFilePath: string): string {
    if (_.startsWith(dependencyFilePath, '/')) {
        // Path of the form /namespace/path/to/dependency.sol
        return dependencyFilePath;
    } else {
        // Dependency is relative to the source file: ./dependency.sol, ../../some/path/dependency.sol, etc.
        // Join the two paths to construct a valid source file id: /namespace/path/to/dependency.sol
        return path.join(path.dirname(sourceFilePath), dependencyFilePath);
    }
}
/**
 * Constructs a system-wide unique identifier for a contract.
 * @param directoryNamespace Namespace of the source file's root contract directory.
 * @param sourceFilePath Path to a source file, relative to contractBaseDir.
 * @return sourceFileId A system-wide unique identifier for contract.
 */
export function constructContractId(directoryNamespace: string, sourceFilePath: string): string {
    const namespacePrefix = !_.isEmpty(directoryNamespace) ? `${directoryNamespace}:` : '';
    const sourceFileName = path.basename(sourceFilePath, constants.SOLIDITY_FILE_EXTENSION);
    const contractId = `${namespacePrefix}${sourceFileName}`;
    return contractId;
}
/**
 * Gets contract data on network or returns if an artifact does not exist.
 * @param artifactsDir Path to the artifacts directory.
 * @param fileName Name of contract file.
 * @return Contract data on network or undefined.
 */
export async function getContractArtifactIfExistsAsync(
    artifactsDir: string,
    fileName: string,
): Promise<ContractArtifact | void> {
    let contractArtifact;
    const contractName = path.basename(fileName, constants.SOLIDITY_FILE_EXTENSION);
    const currentArtifactPath = `${artifactsDir}/${contractName}.json`;
    try {
        const opts = {
            encoding: 'utf8',
        };
        const contractArtifactString = await fsWrapper.readFileAsync(currentArtifactPath, opts);
        contractArtifact = JSON.parse(contractArtifactString);
        return contractArtifact;
    } catch (err) {
        logUtils.log(`Artifact for ${fileName} does not exist`);
        return undefined;
    }
}

/**
 * Creates a directory if it does not already exist.
 * @param artifactsDir Path to the directory.
 */
export async function createDirIfDoesNotExistAsync(dirPath: string): Promise<void> {
    if (!fsWrapper.doesPathExistSync(dirPath)) {
        logUtils.log(`Creating directory at ${dirPath}...`);
        await fsWrapper.mkdirAsync(dirPath);
    }
}

/**
 * Searches Solidity source code for compiler version range.
 * @param  source Source code of contract.
 * @return Solc compiler version range.
 */
export function parseSolidityVersionRange(source: string): string {
    const SOLIDITY_VERSION_RANGE_REGEX = /pragma\s+solidity\s+(.*);/;
    const solcVersionRangeMatch = source.match(SOLIDITY_VERSION_RANGE_REGEX);
    if (_.isNull(solcVersionRangeMatch)) {
        throw new Error('Could not find Solidity version range in source');
    }
    const solcVersionRange = solcVersionRangeMatch[1];
    return solcVersionRange;
}

/**
 * Normalizes the path found in the error message.
 * Example: converts 'base/Token.sol:6:46: Warning: Unused local variable'
 *          to 'Token.sol:6:46: Warning: Unused local variable'
 * This is used to prevent logging the same error multiple times.
 * @param  errMsg An error message from the compiled output.
 * @return The error message with directories truncated from the contract path.
 */
export function getNormalizedErrMsg(errMsg: string): string {
    const SOLIDITY_FILE_EXTENSION_REGEX = /(.*\.sol)/;
    const errPathMatch = errMsg.match(SOLIDITY_FILE_EXTENSION_REGEX);
    if (_.isNull(errPathMatch)) {
        throw new Error('Could not find a path in error message');
    }
    const errPath = errPathMatch[0];
    const baseContract = path.basename(errPath);
    const normalizedErrMsg = errMsg.replace(errPath, baseContract);
    return normalizedErrMsg;
}

/**
 * Parses the contract source code and extracts the dendencies
 * @param  source Contract source code
 * @param sourceFilePath File path of the source code.
 * @return List of dependendencies
 */
export function parseDependencies(source: string, sourceFileId: string): string[] {
    // TODO: Use a proper parser
    const IMPORT_REGEX = /(import\s)/;
    const DEPENDENCY_PATH_REGEX = /"([^"]+)"/; // Source: https://github.com/BlockChainCompany/soljitsu/blob/master/lib/shared.js
    const dependencies: string[] = [];
    const lines = source.split('\n');
    _.forEach(lines, line => {
        if (!_.isNull(line.match(IMPORT_REGEX))) {
            const dependencyMatch = line.match(DEPENDENCY_PATH_REGEX);
            if (!_.isNull(dependencyMatch)) {
                const dependencyPath = dependencyMatch[1];
                const dependencyId = constructDependencyFileId(dependencyPath, sourceFileId);
                dependencies.push(dependencyId);
            }
        }
    });
    return dependencies;
}

/**
 * Callback to resolve dependencies with `solc.compile`.
 * Throws error if contractSources not yet initialized.
 * @param  contractSources Source codes of contracts.
 * @param  sourceFileId ID of the source file.
 * @param  importPath Path of dependency source file.
 * @return Import contents object containing source code of dependency.
 */
export function findImportIfExist(
    contractSources: ContractSources,
    sourceFileId: string,
    importPath: string,
): solc.ImportContents {
    const dependencyFileId = constructDependencyFileId(importPath, sourceFileId);
    const source = contractSources[dependencyFileId];
    if (_.isUndefined(source)) {
        throw new Error(`Contract source not found for ${dependencyFileId}`);
    }
    const importContents: solc.ImportContents = {
        contents: source,
    };
    return importContents;
}