const compileNewFile = async (c) => { // Write a new source file from scratch if (!fs.existsSync('contracts')) { fs.mkdirSync('contracts') } const source = 'contract TestRecompile { uint256 public thing; constructor() { thing = 0x1; } }' fs.writeFileSync(path.join('contracts', 'TestRecompile.sol'), source) const inputHash = util.keccak(source).toString('hex') const compileOutput = await c.compile('TestRecompile.sol') assert(isCompile(compileOutput)) const contract = await c.getContractsManager().getContract('TestRecompile') assert(isContract(contract)) const timestamp = contract.get('timestamp') assert.equal(inputHash, compileOutput.get('TestRecompile').get('inputHash')) assert.equal(inputHash, contract.get('inputHash')) assert.equal(compileOutput.get('TestRecompile').get('timestamp'), timestamp) const output = { inputHash: inputHash, timestamp: timestamp, } LOGGER.debug('OUTPUT', output) return output }
const checkRecompile = async (c, prevInputHash, prevTimestamp) => { const source2 = 'contract TestRecompile { uint256 public thing2; constructor() { thing2 = 0x1; } }' fs.unlinkSync(path.join('contracts', 'TestRecompile.sol')) fs.writeFileSync(path.join('contracts', 'TestRecompile.sol'), source2) const inputHash2 = util.keccak(source2) const compileOutput3 = await c.compile('TestRecompile.sol') assert(isCompile(compileOutput3)) const contract2 = await c.getContractsManager().getContract('TestRecompile') assert(isContract(contract2)) const timestamp2 = await contract2.get('timestamp') assert.notEqual(inputHash2, prevInputHash) LOGGER.debug( 'Timestamps', prevTimestamp, timestamp2 ) assert.ok(Number(timestamp2) > Number(prevTimestamp)) fs.unlinkSync(path.join('contracts', 'TestRecompile.sol')) // Ideally we would rmdir this, but node requires some rimraf magic //fs.unlinkSync('contracts') }
/** * Link a previously compiled contract. Validate dependencies and generate appropriate metadata * @param eth network object connected to a local provider * @param contractOutput the JSON compiled output to deploy * @param depMap is an Immutable Map of library names to deploy IDs * @return the contractOutput augmented with a linkDepMap */ async link(contractName, linkId, _depMap) { const contractOutput = await this.bm.getContract(contractName) if (!isContract(contractOutput)) { throw new Error(`Compile output not found for ${contractName}`) } //assert(isContract(contractOutput), `Compile output not found for ${contractName}` ) const code = '0x' + contractOutput.get('code') //const contractName = contractOutput.get('name') const linkName = `${contractName}-${linkId}` const linksDir = LINKS_DIR //ensureDir(LINKS_DIR) //ensureDir(linksDir) const link = await this.bm.getLink(linkName) const inputHash = keccak(JSON.stringify(contractOutput.toJS())).toString('hex') if (link && link.get('inputHash') === inputHash) { LOGGER.info(`Link ${linkName} is up-to-date with hash ${inputHash}`) return link } else { LOGGER.info(`Link ${linkName} out-of-date with hash ${inputHash}`) } assert(!isLink(link), `Link ${linkName} already exists`) const deployMap = await this.bm.getDeploys() let matches = Map({}) let match while (match = LIB_PATTERN.exec(code)) { assert(match[0], `Match expression not found in ${code}`) assert(match[1], `Match group not found ${code}`) LOGGER.debug(`Match found ${JSON.stringify(match)}`) if (matches.has(match[1])) { assert.equal(match[0], matches.get(match[1])); continue } matches = matches.set(match[1], match[0]) } LOGGER.debug(`matches ${matches.toString()}`) if (matches.count() === 0 && _depMap && _depMap.count() > 0) { throw new Error(`No matches to replace with link map ${JSON.stringify(depMap)}`) } if (matches.count() > 0 && (!_depMap || _depMap.count() == 0)) { throw new Error(`No link map found to replace ${JSON.stringify(matches)}`) } const depMap = Map.isMap(_depMap) ? _depMap : new Map({}) const replacedCode = depMap.reduce((codeSoFar, deployId, contractName) => { // The linkId to replace for the given linkName can also // be a full deployName by itself (e.g. TestInterface=TestImpl-deploy) // in which case, deployId == `TestImpl-deploy` directly // instead of `TestInterface-deploy` const deployName = (deployId.startsWith('deploy')) ? `${contractName}-${deployId}` : deployId const linkPlaceholder = matches.get(contractName) if (!linkPlaceholder) { throw new Error(`Placeholder for dependency ${linkPlaceholder} not found in bytecode.`) } const deployObject = deployMap.get(deployName) if (!isDeploy(deployObject)) { throw new Error(`Deploy ${deployName} not deployed`) } LOGGER.debug('DEPLOY OBJECT', deployObject) const deployAddress = deployObject.get('deployAddress') assert(deployAddress) LOGGER.debug(`Replacing symbols ${linkPlaceholder} with ${deployAddress.slice(2)}`) while (codeSoFar.search(linkPlaceholder) !== -1) { codeSoFar = codeSoFar.replace(linkPlaceholder, deployAddress.slice(2)) } return codeSoFar }, code) assert(replacedCode.match(LIB_PATTERN) === null) // All placeholders should be replaced const now = new Date() const linkOutput = new Map({ type : 'link', name : contractName, linkId : linkId, linkMap : depMap, linkDate : now.toLocaleString(), linkTime : now.getTime(), code : replacedCode, abi : contractOutput.get('abi'), inputHash : inputHash, }) const linkFilePath = `${linksDir}/${linkName}` LOGGER.debug(`Writing link to ${linkFilePath}`) return awaitOutputter(this.outputter(linkFilePath, linkOutput), () => { return linkOutput }) }