diff --git a/examples/lib-complex/index.js b/examples/lib-complex/index.js index 0a3404335..e9cf061c6 100644 --- a/examples/lib-complex/index.js +++ b/examples/lib-complex/index.js @@ -20,7 +20,7 @@ async function setupApp(txParams) { // On-chain, single entry point of the entire application. log.info(`<< Setting up App >> network: ${network}`) const initialVersion = '0.0.1' - return await AppProject.deploy('complex-example', initialVersion, txParams) + return await AppProject.fetchOrDeploy('complex-example', initialVersion, txParams, {}) } async function deployVersion1(project, owner) { diff --git a/packages/cli/src/models/dependency/Dependency.js b/packages/cli/src/models/dependency/Dependency.js index 7bcefb17d..5eef04558 100644 --- a/packages/cli/src/models/dependency/Dependency.js +++ b/packages/cli/src/models/dependency/Dependency.js @@ -29,7 +29,7 @@ export default class Dependency { async deploy(txParams) { const version = semver.coerce(this.version).toString() - const project = await LibProject.deploy(version, txParams) + const project = await LibProject.fetchOrDeploy(version, txParams, {}) await Promise.all( _.map(this.getPackageFile().contracts, (contractName, contractAlias) => { const contractClass = Contracts.getFromNodeModules(this.name, contractName) diff --git a/packages/cli/src/models/network/NetworkAppController.js b/packages/cli/src/models/network/NetworkAppController.js index b5855333c..eee81bb49 100644 --- a/packages/cli/src/models/network/NetworkAppController.js +++ b/packages/cli/src/models/network/NetworkAppController.js @@ -8,10 +8,6 @@ import { allPromisesOrError } from '../../utils/async'; const log = new Logger('NetworkAppController'); export default class NetworkAppController extends NetworkBaseController { - get isDeployed() { - return !!this.appAddress; - } - get appAddress() { return this.networkFile.appAddress } @@ -20,22 +16,26 @@ export default class NetworkAppController extends NetworkBaseController { return this.project.getApp() } - async deploy() { - this.project = await AppProject.deploy(this.packageFile.name, this.currentVersion, this.txParams); - - const app = this.project.getApp() - this.networkFile.app = { address: app.address }; - - const projectPackage = await this.project.getProjectPackage() - this.networkFile.package = { address: projectPackage.address }; + async fetchOrDeploy() { + try { + const { appAddress, packageAddress } = this + + this.project = await AppProject.fetchOrDeploy(this.packageFile.name, this.currentVersion, this.txParams, { appAddress, packageAddress }) + this._registerApp(this.project.getApp()) + this._registerPackage(await this.project.getProjectPackage()) + this._registerVersion(this.currentVersion, await this.project.getCurrentDirectory()) + } catch (deployError) { + this._tryRegisterPartialDeploy(deployError) + } + } - const directory = await this.project.getCurrentDirectory() - this._registerVersion(this.currentVersion, directory.address); + _tryRegisterPartialDeploy({ thepackage, app, directory }) { + super._tryRegisterPartialDeploy({ thepackage, directory }) + if (app) this._registerApp(app) } - async fetch() { - if (!this.isDeployed) throw Error('Your application must be deployed to interact with it.'); - this.project = await AppProject.fetch(this.appAddress, this.packageFile.name, this.txParams); + _registerApp({ address }) { + this.networkFile.app = { address } } async push(reupload = false) { @@ -66,7 +66,7 @@ export default class NetworkAppController extends NetworkBaseController { } async createProxy(packageName, contractAlias, initMethod, initArgs) { - await this.fetch(); + await this.fetchOrDeploy() if (!packageName) packageName = this.packageFile.name; const contractClass = this.localController.getContractClass(packageName, contractAlias); this.checkInitialization(contractClass, initMethod, initArgs); @@ -96,7 +96,7 @@ export default class NetworkAppController extends NetworkBaseController { async setProxiesAdmin(packageName, contractAlias, proxyAddress, newAdmin) { const proxies = this._fetchOwnedProxies(packageName, contractAlias, proxyAddress) if (proxies.length === 0) return []; - await this.fetch(); + await this.fetchOrDeploy() await allPromisesOrError( _.map(proxies, async (proxy) => { @@ -111,7 +111,7 @@ export default class NetworkAppController extends NetworkBaseController { async upgradeProxies(packageName, contractAlias, proxyAddress, initMethod, initArgs) { const proxies = this._fetchOwnedProxies(packageName, contractAlias, proxyAddress) if (proxies.length === 0) return []; - await this.fetch(); + await this.fetchOrDeploy() // Check if there is any migrate method in the contracts and warn the user to call it const contracts = _.uniqWith(_.map(proxies, p => [p.package, p.contract]), _.isEqual) diff --git a/packages/cli/src/models/network/NetworkBaseController.js b/packages/cli/src/models/network/NetworkBaseController.js index e666a594d..f6aa27beb 100644 --- a/packages/cli/src/models/network/NetworkBaseController.js +++ b/packages/cli/src/models/network/NetworkBaseController.js @@ -51,38 +51,38 @@ export default class NetworkBaseController { } async push(reupload = false) { - if (this.isDeployed) { - await this.fetch(); - await this.pushVersion(); - } else { - await this.deploy(); - } + this._checkVersion() + await this.fetchOrDeploy() await Promise.all([ - this.uploadContracts(reupload), + this.uploadContracts(reupload), this.unsetContracts() ]) } - async pushVersion() { + _checkVersion() { const requestedVersion = this.packageFile.version; const currentVersion = this.networkFile.version; + if (requestedVersion !== currentVersion) { log.info(`Current version ${currentVersion}`); log.info(`Creating new version ${requestedVersion}`); - const provider = await this.newVersion(requestedVersion); this.networkFile.contracts = {}; - this._registerVersion(requestedVersion, provider.address); } } - _registerVersion(version, providerAddress) { - this.networkFile.provider = { address: providerAddress }; - this.networkFile.version = version; + _tryRegisterPartialDeploy({ thepackage, directory }) { + if (thepackage) this._registerPackage(thepackage) + if (directory) this._registerVersion(this.currentVersion, directory) + } + + _registerPackage({ address }) { + this.networkFile.package = { address } } - async newVersion(versionName) { - return this.project.newVersion(versionName); + _registerVersion(version, { address }) { + this.networkFile.provider = { address } + this.networkFile.version = version } async uploadContracts(reupload) { diff --git a/packages/cli/src/models/network/NetworkLibController.js b/packages/cli/src/models/network/NetworkLibController.js index 99ebd8d63..828fb6f7e 100644 --- a/packages/cli/src/models/network/NetworkLibController.js +++ b/packages/cli/src/models/network/NetworkLibController.js @@ -4,34 +4,37 @@ import { LibProject } from 'zos-lib'; import NetworkBaseController from './NetworkBaseController'; export default class NetworkLibController extends NetworkBaseController { - get isDeployed() { - return !!this.packageAddress; - } - async createProxy() { throw Error('Cannot create proxy for library project') } - async deploy() { - this.project = await LibProject.deploy(this.currentVersion, this.txParams) - const thepackage = await this.project.getProjectPackage() - this.networkFile.package = { address: thepackage.address } - const provider = await this.project.getCurrentDirectory(); - this._registerVersion(this.currentVersion, provider.address) - } + async fetchOrDeploy() { + try { + const { packageAddress } = this - async fetch() { - if (!this.isDeployed) throw Error('Your application must be deployed to interact with it.'); - this.project = await LibProject.fetch(this.packageAddress, this.currentVersion, this.txParams); + this.project = await LibProject.fetchOrDeploy(this.currentVersion, this.txParams, { packageAddress }) + this._registerPackage(await this.project.getProjectPackage()) + this._registerVersion(this.currentVersion, await this.project.getCurrentDirectory()) + } catch(deployError) { + this._tryRegisterPartialDeploy(deployError) + } } - async newVersion(versionName) { - this.networkFile.frozen = false - return super.newVersion(versionName) + _registerVersion(version, { address }) { + super._registerVersion(version, { address }) } + _checkVersion() { + const requestedVersion = this.packageFile.version + const currentVersion = this.networkFile.version + + if (requestedVersion !== currentVersion) { + this.networkFile.frozen = false + } + super._checkVersion() + } async freeze() { - await this.fetch() + await this.fetchOrDeploy() await this.project.freeze() this.networkFile.frozen = true } diff --git a/packages/cli/src/scripts/status.js b/packages/cli/src/scripts/status.js index d11592d79..63a8a3a31 100644 --- a/packages/cli/src/scripts/status.js +++ b/packages/cli/src/scripts/status.js @@ -25,7 +25,7 @@ async function appInfo(controller) { return false; } - await controller.fetch(); + await controller.fetchOrDeploy(); log.info(`Application is deployed at ${controller.appAddress}`); log.info(`- Package ${controller.packageFile.name} is at ${controller.networkFile.packageAddress}`); return true; @@ -37,7 +37,7 @@ async function libInfo(controller) { return false; } - await controller.fetch(); + await controller.fetchOrDeploy(); log.info(`Library package is deployed at ${controller.packageAddress}`); return true; } diff --git a/packages/cli/test/models/Dependency.test.js b/packages/cli/test/models/Dependency.test.js index d872a9fb1..990d374ac 100644 --- a/packages/cli/test/models/Dependency.test.js +++ b/packages/cli/test/models/Dependency.test.js @@ -72,17 +72,18 @@ contract('Dependency', function() { beforeEach(function() { this.dependency = new Dependency('mock-stdlib', '1.1.0') this.txParams = {} + this.addresses = {} delete this.dependency._packageFile }) describe('#deploy', function() { it('deploys a dependency', function() { const libDeployStub = sinon - .stub(LibProject, 'deploy') + .stub(LibProject, 'fetchOrDeploy') .returns({ setImplementation: () => {} }) this.dependency.deploy(this.txParams) - libDeployStub.should.have.been.calledWithExactly('1.1.0', this.txParams) + libDeployStub.should.have.been.calledWithExactly('1.1.0', this.txParams, this.addresses) sinon.restore() }) }) diff --git a/packages/cli/test/scripts/push.test.js b/packages/cli/test/scripts/push.test.js index b687b47bf..6bb5a2c7c 100644 --- a/packages/cli/test/scripts/push.test.js +++ b/packages/cli/test/scripts/push.test.js @@ -55,7 +55,7 @@ contract('push script', function([_, owner]) { address.should.be.nonzeroAddress; const app = await App.fetch(address); - const hasPackage = await app.hasPackage(this.networkFile.packageFile.name) + const hasPackage = await app.hasPackage(this.networkFile.packageFile.name, defaultVersion) hasPackage.should.be.true }); }; diff --git a/packages/lib/src/app/App.js b/packages/lib/src/app/App.js index 168735438..7096afa0d 100644 --- a/packages/lib/src/app/App.js +++ b/packages/lib/src/app/App.js @@ -40,9 +40,9 @@ export default class App { return { package: thepackage, version } } - async hasPackage(name) { + async hasPackage(name, version = undefined) { const [address, _version] = await this.appContract.getPackage(name) - return !isZeroAddress(address) + return !isZeroAddress(address) && _version === version } async setPackage(name, packageAddress, version) { diff --git a/packages/lib/src/project/AppProject.js b/packages/lib/src/project/AppProject.js index fa9e0e8af..c69d0b7d9 100644 --- a/packages/lib/src/project/AppProject.js +++ b/packages/lib/src/project/AppProject.js @@ -1,26 +1,36 @@ import BasePackageProject from "./BasePackageProject"; import App from "../app/App"; import Package from "../package/Package"; +import { DeployError } from '../utils/errors/DeployError'; import _ from 'lodash'; export default class AppProject extends BasePackageProject { - static async fetch(appAddress, name, txParams) { - const app = await App.fetch(appAddress, txParams) - const packageInfo = await app.getPackage(name) - const project = new this(app, name, packageInfo.version, txParams) - project.package = packageInfo.package - return project - } - - static async deploy(name = 'main', version = '0.1.0', txParams = {}) { - const thepackage = await Package.deploy(txParams) - const directory = await thepackage.newVersion(version) - const app = await App.deploy(txParams) - await app.setPackage(name, thepackage.address, version) - const project = new this(app, name, version, txParams) - project.directory = directory - project.package = thepackage - return project + static async fetchOrDeploy(name = 'main', version = '0.1.0', txParams = {}, { appAddress = undefined, packageAddress = undefined }) { + let thepackage, directory, app + try { + app = appAddress + ? await App.fetch(appAddress, txParams) + : await App.deploy(txParams) + if (packageAddress) { + thepackage = await Package.fetch(packageAddress, txParams) + } else if (await app.hasPackage(name, version)) { + thepackage = (await app.getPackage(name)).package + } else { + thepackage = await Package.deploy(txParams) + } + directory = await thepackage.hasVersion(version) + ? await thepackage.getDirectory(version) + : await thepackage.newVersion(version) + + if (!await app.hasPackage(name, version)) await app.setPackage(name, thepackage.address, version) + const project = new this(app, name, version, txParams) + project.directory = directory + project.package = thepackage + + return project + } catch(deployError) { + throw new DeployError(deployError.message, { thepackage, directory, app }) + } } constructor(app, name = 'main', version = '0.1.0', txParams = {}) { diff --git a/packages/lib/src/project/LibProject.js b/packages/lib/src/project/LibProject.js index d24646b0e..a82784f35 100644 --- a/packages/lib/src/project/LibProject.js +++ b/packages/lib/src/project/LibProject.js @@ -1,5 +1,6 @@ import BasePackageProject from "./BasePackageProject"; import Package from "../package/Package"; +import { DeployError } from '../utils/errors/DeployError'; export default class LibProject extends BasePackageProject { static async fetch(packageAddress, version = '0.1.0', txParams) { @@ -7,12 +8,23 @@ export default class LibProject extends BasePackageProject { return new this(thepackage, version, txParams) } - static async deploy(version = '0.1.0', txParams = {}) { - const thepackage = await Package.deploy(txParams) - const directory = await thepackage.newVersion(version) - const project = new this(thepackage, version, txParams) - project.directory = directory - return project + static async fetchOrDeploy(version = '0.1.0', txParams = {}, { packageAddress = undefined }) { + let thepackage, directory + try { + thepackage = packageAddress + ? await Package.fetch(packageAddress, txParams) + : await Package.deploy(txParams) + directory = await thepackage.hasVersion(version) + ? await thepackage.getDirectory(version) + : await thepackage.newVersion(version) + + const project = new this(thepackage, version, txParams) + project.directory = directory + + return project + } catch(deployError) { + throw new DeployError(deployError.message, { thepackage, directory }) + } } constructor(thepackage, version = '0.1.0', txParams = {}) { diff --git a/packages/lib/src/utils/errors/DeployError.js b/packages/lib/src/utils/errors/DeployError.js new file mode 100644 index 000000000..59e9ed612 --- /dev/null +++ b/packages/lib/src/utils/errors/DeployError.js @@ -0,0 +1,9 @@ +'use strict' + +export class DeployError extends Error { + constructor(message, props) { + super(message) + Object.keys(props).forEach(prop => this[prop] = props[prop]) + } +} + diff --git a/packages/lib/test/src/app/App.test.js b/packages/lib/test/src/app/App.test.js index e63b91680..b912f0e7b 100644 --- a/packages/lib/test/src/app/App.test.js +++ b/packages/lib/test/src/app/App.test.js @@ -51,12 +51,12 @@ contract('App', function (accounts) { beforeEach('setting package', setPackage) it('returns true if exists', async function () { - const hasPackage = await this.app.hasPackage(packageName) + const hasPackage = await this.app.hasPackage(packageName, version) hasPackage.should.be.true }) it('returns false if not exists', async function () { - const hasPackage = await this.app.hasPackage('NOTEXISTS') + const hasPackage = await this.app.hasPackage('NOTEXISTS', version) hasPackage.should.be.false }) }) diff --git a/packages/lib/test/src/project/AppProject.test.js b/packages/lib/test/src/project/AppProject.test.js index 46ad1d015..931dc1faa 100644 --- a/packages/lib/test/src/project/AppProject.test.js +++ b/packages/lib/test/src/project/AppProject.test.js @@ -16,14 +16,15 @@ contract('AppProject', function (accounts) { const newVersion = '0.3.0' beforeEach('deploying', async function () { - this.project = await AppProject.deploy(name, version, { from: owner }) + this.project = await AppProject.fetchOrDeploy(name, version, { from: owner }, {}) this.adminAddress = this.project.getApp().address }); - shouldBehaveLikePackageProject({ + shouldBehaveLikePackageProject({ fetch: async function () { - this.project = await AppProject.fetch(this.project.getApp().address, name, { from: owner }) - }, + this.appAddress = this.project.getApp().address + this.project = await AppProject.fetchOrDeploy(name, version, { from: owner }, { appAddress: this.appAddress }) + }, onNewVersion: function () { it('registers the new package version in the app', async function () { const app = this.project.getApp() @@ -37,7 +38,7 @@ contract('AppProject', function (accounts) { it('has a name', async function () { this.project.name.should.eq(name) }) - } + } }); shouldManageProxies({ @@ -48,4 +49,4 @@ contract('AppProject', function (accounts) { await this.project.setImplementation(ImplV2, "DummyImplementationV2") } }) -}) \ No newline at end of file +}) diff --git a/packages/lib/test/src/project/LibProject.test.js b/packages/lib/test/src/project/LibProject.test.js index d019a86e4..7b13474a8 100644 --- a/packages/lib/test/src/project/LibProject.test.js +++ b/packages/lib/test/src/project/LibProject.test.js @@ -9,13 +9,13 @@ contract('LibProject', function (accounts) { const version = '0.2.0' beforeEach('deploying', async function () { - this.project = await LibProject.deploy(version, { from: owner }) + this.project = await LibProject.fetchOrDeploy(version, { from: owner }, {}) }); - shouldBehaveLikePackageProject({ + shouldBehaveLikePackageProject({ fetch: async function () { const thepackage = await this.project.getProjectPackage() - this.project = await LibProject.fetch(thepackage.address, version, { from: owner }) - } + this.project = await LibProject.fetchOrDeploy(version, { from: owner }, { packageAddress: thepackage.address }) + } }) -}) \ No newline at end of file +})