UNPKG

cc-zos

Version:

Command-line interface for the ZeppelinOS smart contract platform

282 lines (232 loc) 12.2 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _lodash = require('lodash'); var _lodash2 = _interopRequireDefault(_lodash); var _util = require('util'); var _ccZosLib = require('cc-zos-lib'); var _EventsFilter = require('./EventsFilter'); var _EventsFilter2 = _interopRequireDefault(_EventsFilter); var _StatusFetcher = require('./StatusFetcher'); var _StatusFetcher2 = _interopRequireDefault(_StatusFetcher); var _StatusComparator = require('./StatusComparator'); var _StatusComparator2 = _interopRequireDefault(_StatusComparator); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const log = new _ccZosLib.Logger('StatusChecker'); const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; class StatusChecker { static fetch(networkFile, txParams = {}) { const fetcher = new _StatusFetcher2.default(networkFile); return new this(fetcher, networkFile, txParams); } static compare(networkFile, txParams = {}) { const comparator = new _StatusComparator2.default(); return new this(comparator, networkFile, txParams); } constructor(visitor, networkFile, txParams = {}) { this.visitor = visitor; this.txParams = txParams; this.networkFile = networkFile; this.packageName = this.networkFile.packageFile.name; } async setProject() { try { const { packageAddress, appAddress, version } = this.networkFile; if (!this._project) { this._project = this.networkFile.isLib ? await _ccZosLib.LibProject.fetchOrDeploy(this.networkFile.version, this.txParams, { packageAddress }) : await _ccZosLib.AppProject.fetchOrDeploy(this.packageName, this.networkFile.version, this.txParams, { appAddress, packageAddress }); } return this._project; } catch (error) { throw Error(`Cannot fetch project contract from address ${this.networkFile.appAddress}.`, error); } } async call() { await this.setProject(); log.info(`Comparing status of project ${(await this._project.getProjectPackage()).address} ...\n`); if (this.networkFile.isLib) { await this.checkLib(); } else { await this.checkApp(); } this.visitor.onEndChecking(); } async checkApp() { await this.checkVersion(); await this.checkPackage(); await this.checkProvider(); await this.checkImplementations(); await this.checkProxies(); await this.checkDependencies(); } async checkLib() { await this.checkProvider(); await this.checkImplementations(); } async checkVersion() { const observed = this._project.version; const expected = this.networkFile.version; if (!(0, _ccZosLib.semanticVersionEqual)(observed, expected)) this.visitor.onMismatchingVersion(expected, observed); } async checkPackage() { const observed = this._project.package.address; const expected = this.networkFile.packageAddress; if (observed !== expected) this.visitor.onMismatchingPackage(expected, observed); } async checkProvider() { const currentDirectory = await this._project.getCurrentDirectory(); const observed = currentDirectory.address; const expected = this.networkFile.providerAddress; if (observed !== expected) this.visitor.onMismatchingProvider(expected, observed); } async checkDependencies() { const dependenciesInfo = await this._fetchOnChainPackages(); dependenciesInfo.forEach(info => this._checkRemoteDependency(info)); this._checkUnregisteredLocalDependencies(dependenciesInfo); } async checkImplementations() { const implementationsInfo = await this._fetchOnChainImplementations(); await Promise.all(implementationsInfo.map(async info => { const { address } = info; const bytecode = await (0, _util.promisify)(web3.eth.getCode.bind(web3.eth))(address); return (0, _ccZosLib.isSolidityLib)(bytecode) ? this._checkRemoteSolidityLibImplementation(info, bytecode) : this._checkRemoteContractImplementation(info, bytecode); })); this._checkUnregisteredLocalImplementations(implementationsInfo); } async checkProxies() { const proxiesInfo = await this._fetchOnChainProxies(); proxiesInfo.forEach(info => this._checkRemoteProxy(info)); this._checkUnregisteredLocalProxies(proxiesInfo); } _checkRemoteContractImplementation({ alias, address }, bytecode) { if (this.networkFile.hasContract(alias)) { this._checkContractImplementationAddress(alias, address); this._checkContractImplementationBytecode(alias, address, bytecode); } else this.visitor.onMissingRemoteImplementation('none', 'one', { alias, address }); } _checkContractImplementationAddress(alias, address) { const expected = this.networkFile.contract(alias).address; if (address !== expected) this.visitor.onMismatchingImplementationAddress(expected, address, { alias, address }); } _checkContractImplementationBytecode(alias, address, bytecode) { const expected = this.networkFile.contract(alias).bodyBytecodeHash; const observed = (0, _ccZosLib.bytecodeDigest)(bytecode); if (observed !== expected) this.visitor.onMismatchingImplementationBodyBytecode(expected, observed, { alias, address, bodyBytecodeHash: observed }); } _checkRemoteSolidityLibImplementation({ alias, address }, bytecode) { if (this.networkFile.hasSolidityLib(alias)) { this._checkSolidityLibImplementationAddress(alias, address); this._checkSolidityLibImplementationBytecode(alias, address, bytecode); } //TODO: implement missing remote solidity libs validation //else this.visitor.onMissingRemoteImplementation('none', 'one', { alias, address }) } _checkSolidityLibImplementationAddress(alias, address) { const expected = this.networkFile.solidityLib(alias).address; if (address !== expected) this.visitor.onMismatchingImplementationAddress(expected, address, { alias, address }); } _checkSolidityLibImplementationBytecode(alias, address, bytecode) { const expected = this.networkFile.solidityLib(alias).bodyBytecodeHash; const observed = (0, _ccZosLib.bytecodeDigest)((0, _ccZosLib.replaceSolidityLibAddress)(bytecode, address)); if (observed !== expected) this.visitor.onMismatchingImplementationBodyBytecode(expected, observed, { alias, address, bodyBytecodeHash: observed }); } _checkUnregisteredLocalImplementations(implementationsInfo) { const foundAliases = implementationsInfo.map(info => info.alias); this.networkFile.contractAliases.filter(alias => !foundAliases.includes(alias)).forEach(alias => { const { address } = this.networkFile.contract(alias); this.visitor.onUnregisteredLocalImplementation('one', 'none', { alias, address }); }); } _checkRemoteProxy(remoteProxyInfo) { const localProxyInfo = this.networkFile.getProxy(remoteProxyInfo.address); if (localProxyInfo) { this._checkProxyAlias(localProxyInfo, remoteProxyInfo); this._checkProxyImplementation(localProxyInfo, remoteProxyInfo); } else { this.visitor.onMissingRemoteProxy('none', 'one', _extends({}, remoteProxyInfo, { packageName: this.packageName })); } } _checkProxyAlias(localProxyInfo, remoteProxyInfo) { const { alias: observed } = remoteProxyInfo; const { contract: expected, version, package: packageName } = localProxyInfo; if (observed !== expected) this.visitor.onMismatchingProxyAlias(expected, observed, _extends({ packageName, version }, remoteProxyInfo)); } _checkProxyImplementation(localProxyInfo, remoteProxyInfo) { const { implementation: observed } = remoteProxyInfo; const { implementation: expected, version, package: packageName } = localProxyInfo; if (observed !== expected) this.visitor.onMismatchingProxyImplementation(expected, observed, _extends({ packageName, version }, remoteProxyInfo)); } _checkUnregisteredLocalProxies(proxiesInfo) { const foundAddresses = proxiesInfo.map(info => info.address); this.networkFile.getProxies().filter(proxy => !foundAddresses.includes(proxy.address)).forEach(proxy => { const { contract: alias, package: packageName, address, implementation } = proxy; this.visitor.onUnregisteredLocalProxy('one', 'none', { packageName, alias, address, implementation }); }); } _checkRemoteDependency({ name, version, package: address }) { if (this.networkFile.hasDependency(name)) { this._checkDependencyAddress(name, address); this._checkDependencyVersion(name, version); } else this.visitor.onMissingDependency('none', 'one', { name, address, version }); } _checkDependencyAddress(name, address) { const expected = this.networkFile.getDependency(name).package; if (address !== expected) this.visitor.onMismatchingDependencyAddress(expected, address, { name, address }); } _checkDependencyVersion(name, version) { const expected = this.networkFile.getDependency(name).version; if (!(0, _ccZosLib.semanticVersionEqual)(version, expected)) this.visitor.onMismatchingDependencyVersion(expected, version, { name, version }); } _checkUnregisteredLocalDependencies(dependenciesInfo) { const foundDependencies = dependenciesInfo.map(dependency => dependency.name); this.networkFile.dependenciesNames.filter(name => !foundDependencies.includes(name)).forEach(name => { const dependency = this.networkFile.getDependency(name); this.visitor.onUnregisteredDependency('one', 'none', _extends({}, dependency, { name })); }); } async _fetchOnChainImplementations() { const filter = new _EventsFilter2.default(); const directory = await this._project.getCurrentDirectory(); const allEvents = await filter.call(directory.contract, 'ImplementationChanged'); const contractsAlias = allEvents.map(event => event.args.contractName); const events = allEvents.filter((event, index) => contractsAlias.lastIndexOf(event.args.contractName) === index).filter(event => event.args.implementation !== ZERO_ADDRESS).map(event => ({ alias: event.args.contractName, address: event.args.implementation })); return events; } async _fetchOnChainProxies() { const implementationsInfo = await this._fetchOnChainImplementations(); const filter = new _EventsFilter2.default(); const app = this._project.getApp(); const proxyEvents = await filter.call(app.appContract, 'ProxyCreated'); const proxiesInfo = []; await Promise.all(proxyEvents.map(async event => { const address = event.args.proxy; const implementation = await app.getProxyImplementation(address); const matchingImplementations = implementationsInfo.filter(info => info.address === implementation); if (matchingImplementations.length > 1) { this.visitor.onMultipleProxyImplementations('one', matchingImplementations.length, { implementation }); } else if (matchingImplementations.length === 0) { this.visitor.onUnregisteredProxyImplementation('one', 'none', { address, implementation }); } else { const alias = matchingImplementations[0].alias; proxiesInfo.push({ alias, implementation, address }); } })); return proxiesInfo; } async _fetchOnChainPackages() { const filter = new _EventsFilter2.default(); const app = this._project.getApp(); const allEvents = await filter.call(app.appContract, 'PackageChanged'); const filteredEvents = allEvents.filter(event => event.args.package !== ZERO_ADDRESS).filter(event => event.args.providerName !== this.packageName).map(event => ({ name: event.args.providerName, version: event.args.version, package: event.args.package })).reduce((dependencies, dependency) => { dependencies[dependency.name] = dependency; return dependencies; }, {}); return Object.values(filteredEvents); } } exports.default = StatusChecker;