cc-zos
Version:
Command-line interface for the ZeppelinOS smart contract platform
280 lines (230 loc) • 12.6 kB
JavaScript
'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 _NetworkBaseController = require('./NetworkBaseController');
var _NetworkBaseController2 = _interopRequireDefault(_NetworkBaseController);
var _ccZosLib = require('cc-zos-lib');
var _naming = require('../../utils/naming');
var _Dependency = require('../dependency/Dependency');
var _Dependency2 = _interopRequireDefault(_Dependency);
var _async = require('../../utils/async');
var _ProjectDeployer = require('./ProjectDeployer');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const log = new _ccZosLib.Logger('NetworkAppController');
class NetworkAppController extends _NetworkBaseController2.default {
get isLightweight() {
return this.packageFile.isLightweight && !this.appAddress;
}
getDeployer(requestedVersion) {
return this.isLightweight ? new _ProjectDeployer.SimpleProjectDeployer(this, requestedVersion) : new _ProjectDeployer.AppProjectDeployer(this, requestedVersion);
}
get appAddress() {
return this.networkFile.appAddress;
}
get app() {
return this.project.getApp();
}
async toFullApp() {
if (this.appAddress) {
log.info(`Project is already published to ${this.network}`);
return;
}
log.info(`Publishing project to ${this.network}...`);
const simpleProject = await this.fetchOrDeploy(this.currentVersion);
const deployer = new _ProjectDeployer.AppProjectDeployer(this, this.packageVersion);
this.project = await deployer.fromSimpleProject(simpleProject);
log.info(`Publish to ${this.network} successful`);
const proxies = this._fetchOwnedProxies();
if (proxies.length !== 0) {
log.info(`Awaiting confirmations before transferring proxies to published project (this may take a few minutes)`);
const app = this.project.getApp();
await (0, _ccZosLib.awaitConfirmations)(app.contract.transactionHash);
await (0, _ccZosLib.hasBytecode)(app.address);
await this._changeProxiesAdmin(proxies, app.address, simpleProject);
log.info(`${proxies.length} proxies have been successfully transferred`);
}
}
async deployLibs() {
await (0, _async.allPromisesOrError)(_lodash2.default.map(this.packageFile.dependencies, (version, dep) => this.deployLibIfNeeded(dep, version)));
}
async deployLibIfNeeded(depName, depVersion) {
try {
const dependency = new _Dependency2.default(depName, depVersion);
if (dependency.isDeployedOnNetwork(this.network) || this.networkFile.dependencyHasMatchingCustomDeploy(depName)) return;
log.info(`Deploying ${depName} contracts`);
const deployment = await dependency.deploy(this.txParams);
this.networkFile.setDependency(depName, {
package: (await deployment.getProjectPackage()).address,
version: deployment.version,
customDeploy: true
});
} catch (err) {
throw Error(`Failed deployment of dependency ${depName} with error: ${err.message}`);
}
}
async createProxy(packageName, contractAlias, initMethod, initArgs) {
await this.fetchOrDeploy(this.currentVersion);
if (!packageName) packageName = this.packageFile.name;
const contractClass = this.localController.getContractClass(packageName, contractAlias);
this.checkInitialization(contractClass, initMethod, initArgs);
const proxyInstance = await this.project.createProxy(contractClass, { packageName, contractName: contractAlias, initMethod, initArgs });
const implementationAddress = await _ccZosLib.Proxy.at(proxyInstance).implementation();
const packageVersion = packageName === this.packageFile.name ? this.currentVersion : await this.project.getDependencyVersion(packageName);
// FIXME: Shouldn't truffle deployed info correspond to the contract name, and not its alias?
this._updateTruffleDeployedInformation(contractAlias, proxyInstance);
this.networkFile.addProxy(packageName, contractAlias, {
address: proxyInstance.address,
version: (0, _ccZosLib.semanticVersionToString)(packageVersion),
implementation: implementationAddress
});
return proxyInstance;
}
checkInitialization(contractClass, calledInitMethod, calledInitArgs) {
// If there is an initializer called, assume it's ok
if (calledInitMethod) return;
// Otherwise, warn the user to invoke it
const initializeMethod = contractClass.abi.find(fn => fn.type === 'function' && fn.name === 'initialize');
if (!initializeMethod) return;
log.error(`Possible initialization method 'initialize' found in contract. Make sure you initialize your instance.`);
}
async setProxiesAdmin(packageName, contractAlias, proxyAddress, newAdmin) {
const proxies = this._fetchOwnedProxies(packageName, contractAlias, proxyAddress);
if (proxies.length === 0) return [];
await this.fetchOrDeploy(this.currentVersion);
await this._changeProxiesAdmin(proxies, newAdmin);
return proxies;
}
async _changeProxiesAdmin(proxies, newAdmin, project = null) {
if (!project) project = this.project;
await (0, _async.allPromisesOrError)(_lodash2.default.map(proxies, async proxy => {
await project.changeProxyAdmin(proxy.address, newAdmin);
this.networkFile.updateProxy(proxy, proxy => _extends({}, proxy, { admin: newAdmin }));
}));
}
async upgradeProxies(packageName, contractAlias, proxyAddress, initMethod, initArgs) {
const proxies = this._fetchOwnedProxies(packageName, contractAlias, proxyAddress);
if (proxies.length === 0) return [];
await this.fetchOrDeploy(this.currentVersion);
// Check if there is any migrate method in the contracts and warn the user to call it
const contracts = _lodash2.default.uniqWith(_lodash2.default.map(proxies, p => [p.package, p.contract]), _lodash2.default.isEqual);
_lodash2.default.forEach(contracts, ([packageName, contractName]) => this._checkUpgrade(this.localController.getContractClass(packageName, contractName), initMethod, initArgs));
// Update all proxies loaded
await (0, _async.allPromisesOrError)(_lodash2.default.map(proxies, proxy => this._upgradeProxy(proxy, initMethod, initArgs)));
return proxies;
}
async _upgradeProxy(proxy, initMethod, initArgs) {
try {
const name = { packageName: proxy.package, contractName: proxy.contract };
const contractClass = this.localController.getContractClass(proxy.package, proxy.contract);
const currentImplementation = await _ccZosLib.Proxy.at(proxy).implementation();
const contractImplementation = await this.project.getImplementation(name);
const packageVersion = proxy.package === this.packageFile.name ? this.currentVersion : await this.project.getDependencyVersion(proxy.package);
let newImplementation;
if (currentImplementation !== contractImplementation) {
await this.project.upgradeProxy(proxy.address, contractClass, _extends({ initMethod, initArgs }, name));
newImplementation = contractImplementation;
} else {
log.info(`Contract ${proxy.contract} at ${proxy.address} is up to date.`);
newImplementation = currentImplementation;
}
this.networkFile.updateProxy(proxy, proxy => _extends({}, proxy, {
implementation: newImplementation,
version: (0, _ccZosLib.semanticVersionToString)(packageVersion)
}));
} catch (error) {
throw Error(`Proxy ${(0, _naming.toContractFullName)(proxy.package, proxy.contract)} at ${proxy.address} failed to update with error: ${error.message}`);
}
}
_checkUpgrade(contractClass, calledMigrateMethod, calledMigrateArgs) {
// If there is a migration called, assume it's ok
if (calledMigrateMethod) return;
// Otherwise, warn the user to invoke it
const migrateMethod = contractClass.abi.find(fn => fn.type === 'function' && fn.name === 'migrate');
if (!migrateMethod) return;
log.error(`Possible migration method 'migrate' found in contract ${contractClass.contractName}. Remember running the migration after deploying it.`);
}
_fetchOwnedProxies(packageName, contractAlias, proxyAddress) {
let criteriaDescription = '';
if (packageName || contractAlias) criteriaDescription += ` contract ${(0, _naming.toContractFullName)(packageName, contractAlias)}`;
if (proxyAddress) criteriaDescription += ` address ${proxyAddress}`;
const proxies = this.networkFile.getProxies({
package: packageName || (contractAlias ? this.packageFile.name : undefined),
contract: contractAlias,
address: proxyAddress
});
if (_lodash2.default.isEmpty(proxies)) {
log.info(`No contract instances that match${criteriaDescription} were found`);
return [];
}
// TODO: If 'from' is not explicitly set, then we need to retrieve it from the set of current accounts
const expectedOwner = this.isLightweight ? this.txParams.from : this.appAddress;
const ownedProxies = proxies.filter(proxy => !proxy.admin || !expectedOwner || proxy.admin === expectedOwner);
if (_lodash2.default.isEmpty(ownedProxies)) {
log.info(`No contract instances that match${criteriaDescription} are owned by this application`);
}
return ownedProxies;
}
async handleLibsLink() {
await (0, _async.allPromisesOrError)(_lodash2.default.concat(_lodash2.default.map(this.packageFile.dependencies, (version, dep) => this.linkLib(dep, version)), _lodash2.default.map(this.networkFile.dependenciesNamesMissingFromPackage(), dep => this.unlinkLib(dep))));
}
async unlinkLib(depName) {
try {
if (await this.project.hasDependency(depName)) {
log.info(`Unlinking dependency ${depName}`);
await this.project.unsetDependency(depName);
}
this.networkFile.unsetDependency(depName);
} catch (error) {
throw Error(`Failed to unlink dependency ${depName} with error: ${error.message}`);
}
}
async linkLib(depName, depVersion) {
try {
if (this.networkFile.dependencyHasMatchingCustomDeploy(depName)) {
log.info(`Using custom deployment of ${depName}`);
const depInfo = this.networkFile.getDependency(depName);
return await this.project.setDependency(depName, depInfo.package, depInfo.version);
}
if (!this.networkFile.dependencySatisfiesVersionRequirement(depName)) {
const dependencyInfo = new _Dependency2.default(depName, depVersion).getNetworkFile(this.network);
log.info(`Connecting to dependency ${depName} ${dependencyInfo.version}`);
await this.project.setDependency(depName, dependencyInfo.packageAddress, dependencyInfo.version);
const depInfo = { package: dependencyInfo.packageAddress, version: dependencyInfo.version };
this.networkFile.setDependency(depName, depInfo);
}
} catch (error) {
throw Error(`Failed to link dependency ${depName}@${depVersion} with error: ${error.message}`);
}
}
_errorForContractDeployed(packageName, contractAlias) {
if (packageName === this.packageFile.name) {
return this._errorForLocalContractDeployed(contractAlias);
} else if (!this.packageFile.hasDependency(packageName)) {
return `Dependency ${packageName} not found in project.`;
} else if (!this.networkFile.hasDependency(packageName)) {
return `Dependency ${packageName} has not been linked yet. Please run zos push.`;
} else if (!new _Dependency2.default(packageName).getPackageFile().contract(contractAlias)) {
return `Contract ${contractAlias} is not provided by ${packageName}.`;
}
}
_updateTruffleDeployedInformation(contractAlias, implementation) {
const contractName = this.packageFile.contract(contractAlias);
if (contractName) {
const path = _ccZosLib.Contracts.getLocalPath(contractName);
const data = _ccZosLib.FileSystem.parseJson(path);
data.networks = {};
data.networks[implementation.constructor.network_id] = {
links: {},
events: {},
address: implementation.address,
updated_at: Date.now()
};
_ccZosLib.FileSystem.writeJson(path, data);
}
}
}
exports.default = NetworkAppController;