From 242de59fc2003e2360fe1d6568ffd307476fe0f0 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Fri, 15 Jun 2018 20:10:01 -0300 Subject: [PATCH] wip --- contracts/application/AppDirectory.sol | 9 +- contracts/application/BaseApp.sol | 41 ++++---- .../FreezableImplementationDirectory.sol | 2 +- contracts/application/versioning/Release.sol | 25 ----- package.json | 2 +- scripts/test.sh | 8 +- src/app/App.js | 39 +++----- src/app/AppDeployer.js | 24 ++--- src/app/AppProvider.js | 10 +- src/directory/AppDirectory.js | 35 +++++++ src/directory/AppDirectoryDeployer.js | 25 +++++ src/directory/AppDirectoryProvider.js | 20 ++++ .../FreezableImplementationDirectory.js | 32 +++++++ src/directory/ImplementationDirectory.js | 41 ++++++++ .../ImplementationDirectoryDeployer.js} | 43 +++++---- src/index.js | 18 +++- src/package/Package.js | 70 +++++--------- src/package/PackageDeployer.js | 23 ++++- src/package/PackageProvider.js | 17 +++- src/package/PackageWithAppDirectories.js | 29 ++++++ .../PackageWithFreezableDirectories.js | 43 +++++++++ .../PackageWithNonFreezableDirectories.js | 28 ++++++ src/release/Release.js | 49 ---------- src/test/behaviors/ImplementationDirectory.js | 13 +-- src/utils/Contracts.js | 2 +- .../application/AppDirectory.test.js | 16 ++-- .../ImplementationDirectory.test.js | 2 +- .../application/versioning/Release.test.js | 83 ---------------- test/src/app/App.test.js | 36 ++++--- test/src/directory/AppDirectory.test.js | 57 +++++++++++ .../FreezableImplementationDirectory.test.js | 87 +++++++++++++++++ .../directory/ImplementationDirectory.test.js | 67 +++++++++++++ test/src/package/Package.test.js | 96 ------------------- .../package/PackageWithAppDirectories.test.js | 67 +++++++++++++ .../PackageWithFreezableDirectories.test.js | 88 +++++++++++++++++ ...PackageWithNonFreezableDirectories.test.js | 67 +++++++++++++ test/src/release/Release.test.js | 78 --------------- 37 files changed, 872 insertions(+), 520 deletions(-) delete mode 100644 contracts/application/versioning/Release.sol create mode 100644 src/directory/AppDirectory.js create mode 100644 src/directory/AppDirectoryDeployer.js create mode 100644 src/directory/AppDirectoryProvider.js create mode 100644 src/directory/FreezableImplementationDirectory.js create mode 100644 src/directory/ImplementationDirectory.js rename src/{release/ReleaseDeployer.js => directory/ImplementationDirectoryDeployer.js} (53%) create mode 100644 src/package/PackageWithAppDirectories.js create mode 100644 src/package/PackageWithFreezableDirectories.js create mode 100644 src/package/PackageWithNonFreezableDirectories.js delete mode 100644 src/release/Release.js delete mode 100644 test/contracts/application/versioning/Release.test.js create mode 100644 test/src/directory/AppDirectory.test.js create mode 100644 test/src/directory/FreezableImplementationDirectory.test.js create mode 100644 test/src/directory/ImplementationDirectory.test.js delete mode 100644 test/src/package/Package.test.js create mode 100644 test/src/package/PackageWithAppDirectories.test.js create mode 100644 test/src/package/PackageWithFreezableDirectories.test.js create mode 100644 test/src/package/PackageWithNonFreezableDirectories.test.js delete mode 100644 test/src/release/Release.test.js diff --git a/contracts/application/AppDirectory.sol b/contracts/application/AppDirectory.sol index 5048013..d59839b 100644 --- a/contracts/application/AppDirectory.sol +++ b/contracts/application/AppDirectory.sol @@ -7,8 +7,7 @@ import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; /** * @title AppDirectory * @dev Implementation directory with a standard library as a fallback provider. - * If the implementation is not found in the directory, it will search in the - * standard library. + * @dev If the implementation is not found in the directory, it will search in the standard library. */ contract AppDirectory is ImplementationDirectory { /** @@ -32,11 +31,9 @@ contract AppDirectory is ImplementationDirectory { /** * @dev Returns the implementation address for a given contract name. - * If the implementation is not found in the directory, it will search in the - * standard library. + * @dev If the implementation is not found in the directory, it will search in the standard library. * @param contractName Name of the contract. - * @return Address where the contract is implemented, or 0 if it is not - * found. + * @return Address where the contract is implemented, or 0 if it is not found. */ function getImplementation(string contractName) public view returns (address) { address implementation = super.getImplementation(contractName); diff --git a/contracts/application/BaseApp.sol b/contracts/application/BaseApp.sol index 7c1c62f..584e4ac 100644 --- a/contracts/application/BaseApp.sol +++ b/contracts/application/BaseApp.sol @@ -38,6 +38,23 @@ contract BaseApp is Ownable { return getProvider().getImplementation(contractName); } + /** + * @dev Returns the current implementation of a proxy. + * This is needed because only the proxy admin can query it. + * @return The address of the current implementation of the proxy. + */ + function getProxyImplementation(AdminUpgradeabilityProxy proxy) public view returns (address) { + return proxy.implementation(); + } + + /** + * @dev Returns the admin of a proxy. Only the admin can query it. + * @return The address of the current admin of the proxy. + */ + function getProxyAdmin(AdminUpgradeabilityProxy proxy) public view returns (address) { + return proxy.admin(); + } + /** * @dev Creates a new proxy for the given contract. * @param contractName Name of the contract. @@ -53,8 +70,7 @@ contract BaseApp is Ownable { * This is useful to initialize the proxied contract. * @param contractName Name of the contract. * @param data Data to send as msg.data in the low level call. - * It should include the signature and the parameters of the function to be - * called, as described in + * It should include the signature and the parameters of the function to be called, as described in * https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding. * @return Address of the new proxy. */ @@ -79,30 +95,11 @@ contract BaseApp is Ownable { * @param proxy Proxy to be upgraded. * @param contractName Name of the contract. * @param data Data to send as msg.data in the low level call. - * It should include the signature and the parameters of the function to be - * called, as described in + * It should include the signature and the parameters of the function to be called, as described in * https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding. */ function upgradeAndCall(AdminUpgradeabilityProxy proxy, string contractName, bytes data) payable public onlyOwner { address implementation = getImplementation(contractName); proxy.upgradeToAndCall.value(msg.value)(implementation, data); } - - /** - * @dev Returns the current implementation of a proxy. - * This is needed because only the proxy admin can query it. - * @return The address of the current implementation of the proxy. - */ - function getProxyImplementation(AdminUpgradeabilityProxy proxy) public view returns (address) { - return proxy.implementation(); - } - - /** - * @dev Returns the admin of a proxy. - * Only the admin can query it. - * @return The address of the current admin of the proxy. - */ - function getProxyAdmin(AdminUpgradeabilityProxy proxy) public view returns (address) { - return proxy.admin(); - } } diff --git a/contracts/application/versioning/FreezableImplementationDirectory.sol b/contracts/application/versioning/FreezableImplementationDirectory.sol index 09fe32e..a810053 100644 --- a/contracts/application/versioning/FreezableImplementationDirectory.sol +++ b/contracts/application/versioning/FreezableImplementationDirectory.sol @@ -6,7 +6,7 @@ import "./ImplementationDirectory.sol"; * @title FreezableImplementationDirectory * @dev Implementation directory which can be made irreversibly immutable by the owner. */ - contract FreezableImplementationDirectory is ImplementationDirectory { +contract FreezableImplementationDirectory is ImplementationDirectory { /// @dev Mutability state of the directory. bool public frozen; diff --git a/contracts/application/versioning/Release.sol b/contracts/application/versioning/Release.sol deleted file mode 100644 index b54239b..0000000 --- a/contracts/application/versioning/Release.sol +++ /dev/null @@ -1,25 +0,0 @@ -pragma solidity ^0.4.21; - -import "./FreezableImplementationDirectory.sol"; - -/** - * @title Release - * @dev This contract represents a particular standard library version from a developer. - * It has an immutable reference to the implementations of all contracts that comprise it. - */ -contract Release is FreezableImplementationDirectory { - - /** - * @dev Developer address. - * This is the owner of the implementation directory. - */ - address public developer; - - /** - * @dev Constructor function. - * It sets the `msg.sender` as the developer of this release. - */ - function Release() public { - developer = msg.sender; - } -} diff --git a/package.json b/package.json index d86a300..dd25c05 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "ZeppelinOS library", "scripts": { "test": "NODE_ENV=test scripts/test.sh", - "prepack": "truffle compile && babel src --out-dir lib" + "prepack": "npx truffle compile && npx babel-cli src --out-dir lib" }, "repository": { "type": "git", diff --git a/scripts/test.sh b/scripts/test.sh index 6b0720d..ff7396a 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -3,8 +3,8 @@ set -o errexit run_lib_tests() { echo "Testing root project..." - node_modules/.bin/truffle compile - node_modules/.bin/truffle test "$@" + npx truffle compile + npx truffle test "$@" } run_example_tests() { @@ -17,9 +17,9 @@ run_example_tests() { if [ "$SOLIDITY_COVERAGE" = true ]; then echo "Measuring coverage..." - node_modules/.bin/solidity-coverage + npx solidity-coverage if [ "$CONTINUOUS_INTEGRATION" = true ]; then - cat coverage/lcov.info | node_modules/.bin/coveralls + cat coverage/lcov.info | npx coveralls fi else run_lib_tests && run_example_tests diff --git a/src/app/App.js b/src/app/App.js index 5a78689..aa25871 100644 --- a/src/app/App.js +++ b/src/app/App.js @@ -37,7 +37,7 @@ export default class App { this.txParams = txParams } - address() { + get address() { return this._app.address } @@ -50,8 +50,7 @@ export default class App { } async getImplementation(contractName) { - const directory = this.currentDirectory() - return directory.getImplementation(contractName) + return this.package.getImplementation(this.version, contractName) } async getProxyImplementation(proxyAddress) { @@ -59,31 +58,19 @@ export default class App { } async setImplementation(contractClass, contractName) { - log.info(`Setting implementation of ${contractName} in directory...`) - const implementation = await contractClass.new(this.txParams) - const directory = this.currentDirectory() - await directory.setImplementation(contractName, implementation.address, this.txParams) - log.info(` Implementation set: ${implementation.address}`) - return implementation + return this.package.setImplementation(this.version, contractClass, contractName) } async setStdlib(stdlibAddress = 0x0) { - log.info(`Setting stdlib ${stdlibAddress}...`) - await this.currentDirectory().setStdlib(stdlibAddress, this.txParams) - return stdlibAddress + return this.currentDirectory().setStdlib(stdlibAddress) } - async newVersion(versionName, stdlibAddress = 0) { - log.info(`Adding version ${versionName}...`) - const AppDirectory = Contracts.getFromLib('AppDirectory') - const directory = await AppDirectory.new(stdlibAddress, this.txParams) - log.info(` App directory: ${directory.address}`) - await this.package.addVersion(versionName, directory.address, this.txParams) - log.info(` Added version: ${versionName}`) - await this._app.setVersion(versionName, this.txParams) - log.info(` Version set`) - this.directories[versionName] = directory - this.version = versionName + async newVersion(version, stdlibAddress = 0x0) { + const directory = await this.package.newVersion(version, stdlibAddress) + await this._app.setVersion(version, this.txParams) + log.info(`Version set`) + this.directories[version] = directory + this.version = version } async createProxy(contractClass, contractName, initMethodName, initArgs) { @@ -92,11 +79,11 @@ export default class App { ? await this._createProxy(contractName) : await this._createProxyAndCall(contractClass, contractName, initMethodName, initArgs) - log.info(` TX receipt received: ${receipt.transactionHash}`) + log.info(`TX receipt received: ${receipt.transactionHash}`) const UpgradeabilityProxyFactory = Contracts.getFromLib('UpgradeabilityProxyFactory') const logs = decodeLogs(receipt.logs, UpgradeabilityProxyFactory) const address = logs.find(l => l.event === 'ProxyCreated').args.proxy - log.info(` ${contractName} proxy: ${address}`) + log.info(`${contractName} proxy: ${address}`) return new contractClass(address) } @@ -105,7 +92,7 @@ export default class App { const { receipt } = typeof(initArgs) === 'undefined' ? await this._upgradeProxy(proxyAddress, contractName) : await this._upgradeProxyAndCall(proxyAddress, contractClass, contractName, initMethodName, initArgs) - log.info(` TX receipt received: ${receipt.transactionHash}`) + log.info(`TX receipt received: ${receipt.transactionHash}`) } async _createProxy(contractName) { diff --git a/src/app/AppDeployer.js b/src/app/AppDeployer.js index deda8b7..a8dc368 100644 --- a/src/app/AppDeployer.js +++ b/src/app/AppDeployer.js @@ -3,6 +3,8 @@ import Logger from '../utils/Logger' import Contracts from '../utils/Contracts' import App from './App' +import PackageDeployer from "../package/PackageDeployer"; +import AppDirectoryDeployer from "../directory/AppDirectoryDeployer"; const log = new Logger('AppDeployer') @@ -18,8 +20,7 @@ export default class AppDeployer { async deployWithStdlib(version, stdlibAddress) { await this.createFactory() await this.createPackage() - await this.createAppDirectory(stdlibAddress) - await this.addVersion(version) + await this.addVersion(version, stdlibAddress) await this.createApp(version) return new App(this.packagedApp, this.factory, this.appDirectory, this.package, this.version, this.txParams) } @@ -39,23 +40,12 @@ export default class AppDeployer { } async createPackage() { - log.info('Deploying new Package...') - const Package = Contracts.getFromLib('Package') - this.package = await Package.new(this.txParams) - log.info(`Deployed Package ${this.package.address}`) + const deployer = new PackageDeployer(this.txParams); + this.package = await deployer.deployForAppDirectories() } - async createAppDirectory(stdlibAddress) { - log.info('Deploying new AppDirectory...') - const AppDirectory = Contracts.getFromLib('AppDirectory') - this.appDirectory = await AppDirectory.new(stdlibAddress, this.txParams) - log.info(`Deployed AppDirectory ${this.appDirectory.address}`) - } - - async addVersion(version) { - log.info('Adding new version...') + async addVersion(version, stdlibAddress) { this.version = version - await this.package.addVersion(version, this.appDirectory.address, this.txParams) - log.info(`Added version ${version}`) + this.appDirectory = await this.package.newVersion(version, stdlibAddress) } } diff --git a/src/app/AppProvider.js b/src/app/AppProvider.js index e62175c..27ccce1 100644 --- a/src/app/AppProvider.js +++ b/src/app/AppProvider.js @@ -1,5 +1,7 @@ import Contracts from '../utils/Contracts' import App from './App' +import PackageWithAppDirectories from "../package/PackageWithAppDirectories"; +import AppDirectory from "../directory/AppDirectory"; export default class AppProvider { constructor(txParams = {}) { @@ -20,16 +22,14 @@ export default class AppProvider { } async _fetchAppDirectory() { - const AppDirectory = Contracts.getFromLib('AppDirectory') this.version = await this.packagedApp.version() - const appDirectoryAddress = await this.package.getVersion(this.version) - this.appDirectory = new AppDirectory(appDirectoryAddress) + const appDirectory = await this.package.getImplementationDirectory(this.version) + this.appDirectory = await AppDirectory.fetch(appDirectory.address, this.txParams) } async _fetchPackage() { - const Package = Contracts.getFromLib('Package') const packageAddress = await this.packagedApp.package() - this.package = new Package(packageAddress) + this.package = await PackageWithAppDirectories.fetch(packageAddress, this.txParams) } async _fetchFactory() { diff --git a/src/directory/AppDirectory.js b/src/directory/AppDirectory.js new file mode 100644 index 0000000..91da133 --- /dev/null +++ b/src/directory/AppDirectory.js @@ -0,0 +1,35 @@ +'use strict'; + +import Logger from '../utils/Logger' +import AppDirectoryDeployer from './AppDirectoryDeployer' +import ImplementationDirectory from './ImplementationDirectory' +import AppDirectoryProvider from "./AppDirectoryProvider"; + +export default class AppDirectory extends ImplementationDirectory { + + static async fetch(address, txParams = {}) { + const provider = new AppDirectoryProvider(txParams) + return provider.fetch(address) + } + + static async deploy(stdlibAddress = 0x0, txParams = {}) { + const deployer = new AppDirectoryDeployer(txParams) + return deployer.deploy(stdlibAddress) + } + + constructor(directory, txParams = {}) { + const log = new Logger('AppDirectory'); + super(directory, txParams, log) + } + + async stdlib() { + return this.directory.stdlib() + } + + async setStdlib(stdlibAddress) { + this.log.info(`Setting stdlib ${stdlibAddress}...`) + await this.directory.setStdlib(stdlibAddress, this.txParams) + this.log.info(`Stdlib ${stdlibAddress} set`) + return stdlibAddress + } +} diff --git a/src/directory/AppDirectoryDeployer.js b/src/directory/AppDirectoryDeployer.js new file mode 100644 index 0000000..d2e77c0 --- /dev/null +++ b/src/directory/AppDirectoryDeployer.js @@ -0,0 +1,25 @@ +'use strict'; + +import Logger from '../utils/Logger' +import Contracts from '../utils/Contracts' +import AppDirectory from './AppDirectory' + +const log = new Logger('AppDirectoryDeployer') + +export default class AppDirectoryDeployer { + constructor(txParams = {}) { + this.txParams = txParams + } + + async deploy(stdlibAddress = 0x0) { + await this._deployAppDirectory(stdlibAddress) + return new AppDirectory(this.directory, this.txParams) + } + + async _deployAppDirectory(stdlibAddress) { + log.info('Deploying new AppDirectory...') + const AppDirectory = Contracts.getFromLib('AppDirectory') + this.directory = await AppDirectory.new(stdlibAddress, this.txParams) + log.info(`App directory created at ${this.directory.address}`) + } +} diff --git a/src/directory/AppDirectoryProvider.js b/src/directory/AppDirectoryProvider.js new file mode 100644 index 0000000..c9066ae --- /dev/null +++ b/src/directory/AppDirectoryProvider.js @@ -0,0 +1,20 @@ +'use strict'; + +import Contracts from '../utils/Contracts' +import AppDirectory from './AppDirectory' + +export default class AppDirectoryProvider { + constructor(txParams = {}) { + this.txParams = txParams + } + + async fetch(address) { + this._fetchAppDirectory(address) + return new AppDirectory(this.directory, this.txParams) + } + + _fetchAppDirectory(address) { + const AppDirectoryContract = Contracts.getFromLib('AppDirectory') + this.directory = new AppDirectoryContract(address) + } +} diff --git a/src/directory/FreezableImplementationDirectory.js b/src/directory/FreezableImplementationDirectory.js new file mode 100644 index 0000000..50146bd --- /dev/null +++ b/src/directory/FreezableImplementationDirectory.js @@ -0,0 +1,32 @@ +import Logger from '../utils/Logger' +import ImplementationDirectory from "./ImplementationDirectory"; +import ImplementationDirectoryDeployer from './ImplementationDirectoryDeployer' + +export default class FreezableImplementationDirectory extends ImplementationDirectory { + + static async deployLocal(contracts, txParams = {}) { + const deployer = ImplementationDirectoryDeployer.freezable(txParams) + const directory = await deployer.deployLocal(contracts) + return new FreezableImplementationDirectory(directory, txParams) + } + + static async deployDependency(dependencyName, contracts, txParams = {}) { + const deployer = ImplementationDirectoryDeployer.freezable(txParams); + const directory = await deployer.deployDependency(dependencyName, contracts) + return new FreezableImplementationDirectory(directory, txParams) + } + + constructor(directory, txParams = {}) { + const log = new Logger('FreezableImplementationDirectory') + super(directory, txParams, log) + } + + async freeze() { + this.log.info('Freezing implementation directory...') + await this.directory.freeze(this.txParams) + } + + async isFrozen() { + return await this.directory.frozen(this.txParams) + } +} diff --git a/src/directory/ImplementationDirectory.js b/src/directory/ImplementationDirectory.js new file mode 100644 index 0000000..64f9829 --- /dev/null +++ b/src/directory/ImplementationDirectory.js @@ -0,0 +1,41 @@ +import Logger from '../utils/Logger' +import ImplementationDirectoryDeployer from './ImplementationDirectoryDeployer' + +export default class ImplementationDirectory { + + static async deployLocal(contracts, txParams = {}) { + const deployer = ImplementationDirectoryDeployer.nonFreezable(txParams) + const directory = await deployer.deployLocal(contracts); + return new ImplementationDirectory(directory, txParams) + } + + static async deployDependency(dependencyName, contracts, txParams = {}) { + const deployer = ImplementationDirectoryDeployer.nonFreezable(txParams) + const directory = await deployer.deployDependency(dependencyName, contracts) + return new ImplementationDirectory(directory, txParams) + } + + constructor(directory, txParams = {}, log = new Logger('ImplementationDirectory')) { + this.directory = directory + this.txParams = txParams + this.log = log + } + + get address() { + return this.directory.address + } + + async owner() { + return this.directory.owner(this.txParams) + } + + async getImplementation(contractName) { + return await this.directory.getImplementation(contractName, this.txParams) + } + + async setImplementation(contractName, implementationAddress) { + this.log.info(`Setting ${contractName} implementation ${implementationAddress}`) + await this.directory.setImplementation(contractName, implementationAddress, this.txParams) + this.log.info(`Implementation set ${implementationAddress}`) + } +} diff --git a/src/release/ReleaseDeployer.js b/src/directory/ImplementationDirectoryDeployer.js similarity index 53% rename from src/release/ReleaseDeployer.js rename to src/directory/ImplementationDirectoryDeployer.js index b536809..dd1a37c 100644 --- a/src/release/ReleaseDeployer.js +++ b/src/directory/ImplementationDirectoryDeployer.js @@ -1,41 +1,50 @@ -import Release from './Release' import Logger from '../utils/Logger' import Contracts from '../utils/Contracts' -const log = new Logger('ReleaseDeployer') +const log = new Logger('ImplementationDirectoryDeployer') -export default class ReleaseDeployer { - constructor(txParams = {}) { +export default class ImplementationDirectoryDeployer { + static freezable(txParams = {}) { + const contractClass = Contracts.getFromLib('FreezableImplementationDirectory') + return new ImplementationDirectoryDeployer(contractClass, txParams) + } + + static nonFreezable(txParams = {}) { + const contractClass = Contracts.getFromLib('ImplementationDirectory') + return new ImplementationDirectoryDeployer(contractClass, txParams) + } + + constructor(contractClass, txParams = {}) { + this.contractClass = contractClass this.txParams = txParams } - async deployLocal(contracts) { - await this.deployRelease() + async deployLocal(contracts = []) { + await this.deployImplementationDirectory() const deployMethod = async contractName => this._deployLocalContract(contractName) await this.deployAndRegisterContracts(contracts, deployMethod) - return new Release(this.release, this.txParams) + return this.directory } - async deployDependency(dependencyName, contracts) { - await this.deployRelease() + async deployDependency(dependencyName, contracts = []) { + await this.deployImplementationDirectory() const deployMethod = async contractName => this._deployDependencyContract(dependencyName, contractName) await this.deployAndRegisterContracts(contracts, deployMethod) - return new Release(this.release, this.txParams) + return this.directory } - async deployRelease() { - log.info("Deploying a new Release...") - const Release = Contracts.getFromLib('Release') - this.release = await Release.new(this.txParams) - log.info(`Deployed at ${this.release.address}`) + async deployImplementationDirectory() { + log.info(`Deploying a new ${this.contractClass.contractName}...`) + this.directory = await this.contractClass.new(this.txParams) + log.info(`Deployed at ${this.directory.address}`) } async deployAndRegisterContracts(contracts, deployMethod) { await Promise.all(contracts.map(async contract => { const { alias: contractAlias, name: contractName } = contract const implementation = await deployMethod(contractName) - log.info('Registering implementation in release...') - await this.release.setImplementation(contractAlias, implementation.address, this.txParams) + log.info('Registering implementation in implementation directory...') + await this.directory.setImplementation(contractAlias, implementation.address, this.txParams) })) } diff --git a/src/index.js b/src/index.js index 411e942..bce892c 100644 --- a/src/index.js +++ b/src/index.js @@ -17,8 +17,14 @@ const assertRevert = helpers.assertRevert // model objects import App from './app/App' -import Package from './package/Package' -import Release from './release/Release' + +import PackageWithAppDirectories from './package/PackageWithAppDirectories' +import PackageWithNonFreezableDirectories from './package/PackageWithNonFreezableDirectories' +import PackageWithFreezableDirectories from './package/PackageWithFreezableDirectories' + +import AppDirectory from './directory/AppDirectory' +import ImplementationDirectory from './directory/ImplementationDirectory' +import FreezableImplementationDirectory from './directory/FreezableImplementationDirectory' export { version, @@ -31,6 +37,10 @@ export { FileSystem, Contracts, App, - Package, - Release, + ImplementationDirectory, + FreezableImplementationDirectory, + AppDirectory, + PackageWithAppDirectories, + PackageWithNonFreezableDirectories, + PackageWithFreezableDirectories, } diff --git a/src/package/Package.js b/src/package/Package.js index 7467f58..7aaa2b2 100644 --- a/src/package/Package.js +++ b/src/package/Package.js @@ -1,74 +1,54 @@ import Logger from '../utils/Logger' -import Contracts from '../utils/Contracts' - -import PackageDeployer from './PackageDeployer' -import PackageProvider from './PackageProvider' const log = new Logger('Package') export default class Package { - static async fetch(address, txParams = {}) { - const provider = new PackageProvider(txParams) - return await provider.from(address) - } - - static async deploy(txParams = {}) { - const deployer = new PackageDeployer(txParams) - return await deployer.deploy() - } - constructor(_package, txParams = {}) { this.package = _package this.txParams = txParams } - address() { + get address() { return this.package.address } async hasVersion(version) { - return await this.package.hasVersion(version, this.txParams) + return this.package.hasVersion(version, this.txParams) } - async getRelease(version) { - const releaseAddress = await this.package.getVersion(version) - const Release = Contracts.getFromLib('Release') - return new Release(releaseAddress) + async getImplementation(version, contractName) { + const implementationDirectory = await this.getImplementationDirectory(version) + return implementationDirectory.getImplementation(contractName) } - async newVersion(version) { - log.info('Adding new version...') - const Release = Contracts.getFromLib('Release') - const release = await Release.new(this.txParams) - await this.package.addVersion(version, release.address, this.txParams) - log.info(' Added version:', version) - return release + async setImplementation(version, contractClass, contractName) { + log.info(`Setting implementation of ${contractName} in version ${version}...`) + const implementation = await contractClass.new(this.txParams) + const directory = await this.getImplementationDirectory(version) + await directory.setImplementation(contractName, implementation.address, this.txParams) + log.info(`Implementation set ${implementation.address}`) + return implementation } - async isFrozen(version) { - const release = await this.getRelease(version) - return await release.frozen() + async newVersion(version, stdlibAddress) { + log.info('Adding new version...') + const directory = await this.newDirectory(stdlibAddress) + await this.package.addVersion(version, directory.address, this.txParams) + log.info(`Added version ${version}`) + return directory } - async freeze(version) { - log.info('Freezing new version...') - const release = await this.getRelease(version) - await release.freeze(this.txParams) - log.info(' Release frozen') + async getImplementationDirectory(version) { + const directoryAddress = await this.package.getVersion(version) + return this.wrapImplementationDirectory(directoryAddress) } - async getImplementation(version, contractName) { - const release = await this.getRelease(version) - return await release.getImplementation(contractName) + async wrapImplementationDirectory() { + throw Error('Cannot call abstract method wrapImplementationDirectory()') } - async setImplementation(version, contractClass, contractName) { - log.info(`Setting implementation of ${contractName} in version ${version}...`) - const implementation = await contractClass.new(this.txParams) - const release = await this.getRelease(version) - await release.setImplementation(contractName, implementation.address, this.txParams) - log.info(` Implementation set: ${implementation.address}`) - return implementation + async newDirectory() { + throw Error('Cannot call abstract method newDirectory()') } } diff --git a/src/package/PackageDeployer.js b/src/package/PackageDeployer.js index 46603f8..c1d6097 100644 --- a/src/package/PackageDeployer.js +++ b/src/package/PackageDeployer.js @@ -1,5 +1,10 @@ -import Package from './Package' +import Logger from '../utils/Logger' import Contracts from '../utils/Contracts' +import PackageWithAppDirectories from './PackageWithAppDirectories' +import PackageWithFreezableDirectories from './PackageWithFreezableDirectories' +import PackageWithNonFreezableDirectories from './PackageWithNonFreezableDirectories' + +const log = new Logger('PackageDeployer') export default class PackageDeployer { constructor(txParams = {}) { @@ -7,12 +12,24 @@ export default class PackageDeployer { } async deploy() { - await this._createPackage(); - return new Package(this.package, this.txParams) + await this._createPackage() + return new PackageWithNonFreezableDirectories(this.package, this.txParams) + } + + async deployForFreezableDirectories() { + await this._createPackage() + return new PackageWithFreezableDirectories(this.package, this.txParams) + } + + async deployForAppDirectories() { + await this._createPackage() + return new PackageWithAppDirectories(this.package, this.txParams) } async _createPackage() { + log.info('Deploying new Package...') const Package = Contracts.getFromLib('Package') this.package = await Package.new(this.txParams) + log.info(`Deployed Package ${this.package.address}`) } } diff --git a/src/package/PackageProvider.js b/src/package/PackageProvider.js index 9aac5d0..34d5a68 100644 --- a/src/package/PackageProvider.js +++ b/src/package/PackageProvider.js @@ -1,14 +1,27 @@ import Package from './Package' import Contracts from '../utils/Contracts' +import PackageWithAppDirectories from './PackageWithAppDirectories' +import PackageWithFreezableDirectories from './PackageWithFreezableDirectories' +import PackageWithNonFreezableDirectories from './PackageWithNonFreezableDirectories' export default class PackageProvider { constructor(txParams = {}) { this.txParams = txParams } - from(address) { + fetch(address) { this._fetchPackage(address); - return new Package(this.package, this.txParams) + return new PackageWithNonFreezableDirectories(this.package, this.txParams) + } + + fetchForFreezableDirectories(address) { + this._fetchPackage(address); + return new PackageWithFreezableDirectories(this.package, this.txParams) + } + + fetchForAppDirectories(address) { + this._fetchPackage(address); + return new PackageWithAppDirectories(this.package, this.txParams) } _fetchPackage(address) { diff --git a/src/package/PackageWithAppDirectories.js b/src/package/PackageWithAppDirectories.js new file mode 100644 index 0000000..450932c --- /dev/null +++ b/src/package/PackageWithAppDirectories.js @@ -0,0 +1,29 @@ +import Contracts from '../utils/Contracts' + +import Package from './Package' +import PackageDeployer from './PackageDeployer' +import PackageProvider from './PackageProvider' +import AppDirectoryDeployer from '../directory/AppDirectoryDeployer' + +export default class PackageWithAppDirectories extends Package { + + static async fetch(address, txParams = {}) { + const provider = new PackageProvider(txParams) + return await provider.fetchForAppDirectories(address) + } + + static async deploy(txParams = {}) { + const deployer = new PackageDeployer(txParams) + return await deployer.deployForAppDirectories() + } + + async wrapImplementationDirectory(directoryAddress) { + const AppDirectory = Contracts.getFromLib('AppDirectory'); + return new AppDirectory(directoryAddress) + } + + async newDirectory(stdlibAddress) { + const deployer = new AppDirectoryDeployer(this.txParams) + return deployer.deploy(stdlibAddress) + } +} diff --git a/src/package/PackageWithFreezableDirectories.js b/src/package/PackageWithFreezableDirectories.js new file mode 100644 index 0000000..218ee1a --- /dev/null +++ b/src/package/PackageWithFreezableDirectories.js @@ -0,0 +1,43 @@ +import Logger from '../utils/Logger' +import Contracts from '../utils/Contracts' + +import Package from './Package' +import PackageDeployer from './PackageDeployer' +import PackageProvider from './PackageProvider' +import ImplementationDirectoryDeployer from '../directory/ImplementationDirectoryDeployer' + +const log = new Logger('Package') + +export default class PackageWithFreezableDirectories extends Package { + + static async fetch(address, txParams = {}) { + const provider = new PackageProvider(txParams) + return await provider.fetchForFreezableDirectories(address) + } + + static async deploy(txParams = {}) { + const deployer = new PackageDeployer(txParams) + return await deployer.deployForFreezableDirectories() + } + + async wrapImplementationDirectory(directoryAddress) { + const FreezableImplementationDirectory = Contracts.getFromLib('FreezableImplementationDirectory'); + return new FreezableImplementationDirectory(directoryAddress) + } + + async newDirectory() { + return ImplementationDirectoryDeployer.freezable(this.txParams).deployLocal() + } + + async isFrozen(version) { + const directory = await this.getImplementationDirectory(version) + return await directory.frozen() + } + + async freeze(version) { + log.info('Freezing new implementation directory...') + const directory = await this.getImplementationDirectory(version) + await directory.freeze(this.txParams) + log.info('Implementation directory frozen') + } +} diff --git a/src/package/PackageWithNonFreezableDirectories.js b/src/package/PackageWithNonFreezableDirectories.js new file mode 100644 index 0000000..e339147 --- /dev/null +++ b/src/package/PackageWithNonFreezableDirectories.js @@ -0,0 +1,28 @@ +import Contracts from '../utils/Contracts' + +import Package from './Package' +import PackageDeployer from './PackageDeployer' +import PackageProvider from './PackageProvider' +import ImplementationDirectoryDeployer from "../directory/ImplementationDirectoryDeployer"; + +export default class PackageWithNonFreezableDirectories extends Package { + + static async fetch(address, txParams = {}) { + const provider = new PackageProvider(txParams) + return await provider.fetch(address) + } + + static async deploy(txParams = {}) { + const deployer = new PackageDeployer(txParams) + return await deployer.deploy() + } + + async wrapImplementationDirectory(directoryAddress) { + const ImplementationDirectory = Contracts.getFromLib('ImplementationDirectory'); + return new ImplementationDirectory(directoryAddress) + } + + async newDirectory() { + return ImplementationDirectoryDeployer.nonFreezable(this.txParams).deployLocal() + } +} diff --git a/src/release/Release.js b/src/release/Release.js deleted file mode 100644 index e007574..0000000 --- a/src/release/Release.js +++ /dev/null @@ -1,49 +0,0 @@ -import Logger from '../utils/Logger' - -import ReleaseDeployer from './ReleaseDeployer' - -const log = new Logger('Release') - -export default class Release { - - static async deployLocal(contracts, txParams = {}) { - const deployer = new ReleaseDeployer(txParams); - return await deployer.deployLocal(contracts); - } - - static async deployDependency(dependencyName, contracts, txParams = {}) { - const deployer = new ReleaseDeployer(txParams); - return await deployer.deployDependency(dependencyName, contracts); - } - - constructor(release, txParams = {}) { - this._release = release - this.txParams = txParams - } - - address() { - return this._release.address - } - - async owner() { - return await this._release.owner(this.txParams) - } - - async freeze() { - log.info('Freezing release...') - await this._release.freeze(this.txParams) - } - - async isFrozen() { - return await this._release.frozen(this.txParams) - } - - async getImplementation(contractName) { - return await this._release.getImplementation(contractName, this.txParams) - } - - async setImplementation(contractName, implementationAddress) { - log.info(`Setting ${contractName} implementation ${implementationAddress}`) - return await this._release.setImplementation(contractName, implementationAddress, this.txParams) - } -} diff --git a/src/test/behaviors/ImplementationDirectory.js b/src/test/behaviors/ImplementationDirectory.js index 3ef4561..f14eb5b 100644 --- a/src/test/behaviors/ImplementationDirectory.js +++ b/src/test/behaviors/ImplementationDirectory.js @@ -20,12 +20,7 @@ export default function shouldBehaveLikeImplementationDirectory(owner, anotherAd describe('when registering a contract', function () { beforeEach('registering the contract', async function () { - const { logs } = await this.directory.setImplementation( - contractName, - this.implementation_v0, - { from } - ) - + const { logs } = await this.directory.setImplementation(contractName, this.implementation_v0, { from }) this.logs = logs }) @@ -78,11 +73,7 @@ export default function shouldBehaveLikeImplementationDirectory(owner, anotherAd const contractName = 'ERC721' beforeEach('registering the contract', async function () { - await this.directory.setImplementation( - contractName, - this.implementation_v0, - { from: owner } - ) + await this.directory.setImplementation(contractName, this.implementation_v0, { from: owner }) }) describe('when the sender is the directory owner', function () { diff --git a/src/utils/Contracts.js b/src/utils/Contracts.js index 82cdebb..cf05fc8 100644 --- a/src/utils/Contracts.js +++ b/src/utils/Contracts.js @@ -14,7 +14,7 @@ const DEFAULT_COVERAGE_TX_PARAMS = { export default { getFromLocal(contractName) { - const buildDir = `${process.cwd()}/build/contracts/` + const buildDir = `${process.cwd()}/build/contracts` return this._getFromBuildDir(buildDir, contractName) }, diff --git a/test/contracts/application/AppDirectory.test.js b/test/contracts/application/AppDirectory.test.js index f0c2cc4..cc9dd39 100644 --- a/test/contracts/application/AppDirectory.test.js +++ b/test/contracts/application/AppDirectory.test.js @@ -6,10 +6,10 @@ import assertRevert from '../../../src/test/helpers/assertRevert' import shouldBehaveLikeImplementationDirectory from '../../../src/test/behaviors/ImplementationDirectory' const AppDirectory = Contracts.getFromLocal('AppDirectory') -const ImplementationDirectory = Contracts.getFromLocal('ImplementationDirectory') const DummyImplementation = Contracts.getFromLocal('DummyImplementation') +const ImplementationDirectory = Contracts.getFromLocal('ImplementationDirectory') -contract('AppDirectory', ([_, owner, stdlibOwner, anotherAddress]) => { +contract('AppDirectory', ([_, appOwner, stdlibOwner, anotherAddress]) => { before(async function () { this.implementation_v0 = (await DummyImplementation.new()).address this.implementation_v1 = (await DummyImplementation.new()).address @@ -17,11 +17,11 @@ contract('AppDirectory', ([_, owner, stdlibOwner, anotherAddress]) => { }) beforeEach(async function () { - this.directory = await AppDirectory.new(0x0, { from: owner }) + this.directory = await AppDirectory.new(0x0, { from: appOwner }) this.stdlib = await ImplementationDirectory.new({ from: stdlibOwner }) }) - shouldBehaveLikeImplementationDirectory(owner, anotherAddress) + shouldBehaveLikeImplementationDirectory(appOwner, anotherAddress) describe('getImplementation', function () { const contractName = 'ERC721' @@ -29,7 +29,7 @@ contract('AppDirectory', ([_, owner, stdlibOwner, anotherAddress]) => { describe('when no stdlib was set', function () { describe('when the requested contract was registered in the directory', function () { beforeEach(async function () { - await this.directory.setImplementation(contractName, this.implementation_v0, { from: owner }) + await this.directory.setImplementation(contractName, this.implementation_v0, { from: appOwner }) }) it('returns the directory implementation', async function () { @@ -48,12 +48,12 @@ contract('AppDirectory', ([_, owner, stdlibOwner, anotherAddress]) => { describe('when a stdlib was set', function () { beforeEach(async function () { - await this.directory.setStdlib(this.stdlib.address, { from: owner }) + await this.directory.setStdlib(this.stdlib.address, { from: appOwner }) }) describe('when the requested contract was registered in the directory', function () { beforeEach(async function () { - await this.directory.setImplementation(contractName, this.implementation_v0, { from: owner }) + await this.directory.setImplementation(contractName, this.implementation_v0, { from: appOwner }) }) describe('when the requested contract was registered in the stdlib', function () { @@ -99,7 +99,7 @@ contract('AppDirectory', ([_, owner, stdlibOwner, anotherAddress]) => { describe('setStdlib', function () { describe('when the sender is the owner', function () { - const from = owner + const from = appOwner beforeEach(async function () { await this.directory.setStdlib(this.stdlib.address, { from }) diff --git a/test/contracts/application/versioning/ImplementationDirectory.test.js b/test/contracts/application/versioning/ImplementationDirectory.test.js index 22cbbd7..82266d4 100644 --- a/test/contracts/application/versioning/ImplementationDirectory.test.js +++ b/test/contracts/application/versioning/ImplementationDirectory.test.js @@ -4,8 +4,8 @@ require('../../../setup') import Contracts from '../../../../src/utils/Contracts' import shouldBehaveLikeImplementationDirectory from '../../../../src/test/behaviors/ImplementationDirectory' -const ImplementationDirectory = Contracts.getFromLocal('ImplementationDirectory') const DummyImplementation = Contracts.getFromLocal('DummyImplementation') +const ImplementationDirectory = Contracts.getFromLocal('ImplementationDirectory') contract('ImplementationDirectory', function([_, owner, anotherAddress]) { beforeEach(async function () { diff --git a/test/contracts/application/versioning/Release.test.js b/test/contracts/application/versioning/Release.test.js deleted file mode 100644 index 2a348b5..0000000 --- a/test/contracts/application/versioning/Release.test.js +++ /dev/null @@ -1,83 +0,0 @@ -'use strict'; -require('../../../setup') - -import Contracts from '../../../../src/utils/Contracts' -import assertRevert from '../../../../src/test/helpers/assertRevert' -import shouldBehaveLikeImplementationDirectory from '../../../../src/test/behaviors/ImplementationDirectory' - -const DummyImplementation = Contracts.getFromLocal('DummyImplementation'); -const Release = Contracts.getFromLocal('Release'); - -contract('Release', ([_, developer, anotherAddress]) => { - - beforeEach("initializing a new release", async function () { - this.release = await Release.new({ from: developer }); - }); - - it('has a developer', async function () { - const instanceDeveloper = await this.release.developer(); - instanceDeveloper.should.be.equal(developer); - }); - - it('starts unfrozen', async function () { - const frozen = await this.release.frozen() - assert.isFalse(frozen) - }) - - describe('freeze', function () { - describe('when the sender is not the developer', function () { - const from = developer - - describe('when it is not frozen', function () { - it('can be frozen', async function () { - await this.release.freeze({ from }) - const frozen = await this.release.frozen() - assert.isTrue(frozen) - }) - }) - - describe('when it is frozen', function () { - beforeEach(async function () { - await this.release.freeze({ from }) - }) - - it('cannot be re-frozen', async function () { - await assertRevert(this.release.freeze({ from })) - }) - }) - }) - - describe('when the sender is not the developer', function () { - const from = anotherAddress - - it('reverts', async function () { - await assertRevert(this.release.freeze({ from })) - }) - }) - }) - - describe('setImplementation', function () { - beforeEach(async function() { - this.implementation_v0 = (await DummyImplementation.new()).address - this.implementation_v1 = (await DummyImplementation.new()).address - }) - - describe('when it is not frozen', function () { - beforeEach(function () { - this.directory = this.release - }) - - shouldBehaveLikeImplementationDirectory(developer, anotherAddress) - }) - - describe('when it is frozen', function () { - beforeEach(async function () { - await this.release.freeze({ from: developer }) - }) - - it('reverts', async function () { - await assertRevert(this.release.setImplementation('ERC721', this.implementation_v1, { from: developer })) - }) - }) - }) -}) diff --git a/test/src/app/App.test.js b/test/src/app/App.test.js index 1a03ede..8e3c945 100644 --- a/test/src/app/App.test.js +++ b/test/src/app/App.test.js @@ -4,20 +4,20 @@ require('../../setup') import App from '../../../src/app/App'; import Contracts from '../../../src/utils/Contracts' -const AppDirectory = Contracts.getFromLocal('AppDirectory'); const Impl = Contracts.getFromLocal('Impl'); const ImplV1 = Contracts.getFromLocal('DummyImplementation'); const ImplV2 = Contracts.getFromLocal('DummyImplementationV2'); +const AppDirectory = Contracts.getFromLocal('AppDirectory'); +const ImplementationDirectory = Contracts.getFromLocal('ImplementationDirectory'); contract('App', function ([_, owner]) { const txParams = { from: owner } const initialVersion = '1.0'; const contractName = 'Impl'; - const stdlibAddress = '0x0000000000000000000000000000000000000010'; const shouldInitialize = function () { it('deploys all contracts', async function() { - this.app.address().should.not.be.null; + this.app.address.should.not.be.null; this.app.factory.address.should.not.be.null; this.app.package.address.should.not.be.null; }); @@ -36,21 +36,25 @@ contract('App', function ([_, owner]) { }); it('returns the current directory', async function () { - this.app.currentDirectory().address.should.be.not.null; + const directory = await this.app.currentDirectory() + directory.address.should.be.not.null; }); }; const shouldConnectToStdlib = function () { it('should connect current directory to stdlib', async function () { - const address = await this.app.package.getVersion(this.app.version); - const currentDirectory = AppDirectory.at(address) - const currentStdlib = await currentDirectory.stdlib() + const appDirectory = await this.app.package.getImplementationDirectory(this.app.version) + const currentStdlib = await appDirectory.stdlib() const stdlib = await this.app.currentStdlib(); stdlib.should.be.eq(currentStdlib) }); }; + beforeEach('deploying stdlib', async function () { + this.stdlib = await ImplementationDirectory.new({ from: owner }) + }); + describe('without stdlib', function () { beforeEach('deploying', async function () { this.app = await App.deploy(initialVersion, txParams) @@ -60,9 +64,9 @@ contract('App', function ([_, owner]) { shouldInitialize(); }); - describe('connect', function () { + describe('fetch', function () { beforeEach('connecting to existing instance', async function () { - this.app = await App.fetch(this.app.address(), txParams); + this.app = await App.fetch(this.app.address, txParams); }); shouldInitialize(); @@ -86,8 +90,10 @@ contract('App', function ([_, owner]) { }); it('returns the current directory', async function () { - const currentDirectory = await this.app.package.getVersion(newVersion); - this.app.currentDirectory().address.should.eq(currentDirectory); + const appDirectory = await this.app.package.getImplementationDirectory(this.app.version) + + const currentDirectory = this.app.currentDirectory() + currentDirectory.address.should.eq(appDirectory.address) }); }); @@ -208,7 +214,7 @@ contract('App', function ([_, owner]) { describe('setStdlib', function () { beforeEach('setting stdlib from name', async function () { - await this.app.setStdlib(stdlibAddress); + await this.app.setStdlib(this.stdlib.address); }); shouldConnectToStdlib(); @@ -217,7 +223,7 @@ contract('App', function ([_, owner]) { describe('with stdlib', function () { beforeEach('deploying', async function () { - this.app = await App.deployWithStdlib(initialVersion, stdlibAddress, txParams); + this.app = await App.deployWithStdlib(initialVersion, this.stdlib.address, txParams); }); describe('deploy', function () { @@ -225,9 +231,9 @@ contract('App', function ([_, owner]) { shouldConnectToStdlib(); }); - describe('connect', function () { + describe('fetch', function () { beforeEach('connecting to existing instance', async function () { - this.app = await App.fetch(this.app.address(), txParams); + this.app = await App.fetch(this.app.address, txParams); }); shouldInitialize(); diff --git a/test/src/directory/AppDirectory.test.js b/test/src/directory/AppDirectory.test.js new file mode 100644 index 0000000..9714e33 --- /dev/null +++ b/test/src/directory/AppDirectory.test.js @@ -0,0 +1,57 @@ +'use strict' +require('../../setup') + +import Contracts from '../../../src/utils/Contracts' +import AppDirectory from '../../../src/directory/AppDirectory' + +const DummyImplementation = Contracts.getFromLocal('DummyImplementation') +const ImplementationDirectory = Contracts.getFromLocal('ImplementationDirectory') + +contract('AppDirectory', ([_, appOwner, stdlibOwner, anotherAddress]) => { + const txParams = { from: appOwner } + + beforeEach('deploying app directory', async function () { + this.stdlib = await ImplementationDirectory.new({ from: stdlibOwner }) + this.directory = await AppDirectory.deploy(this.stdlib.address, txParams) + }) + + it('has an address', async function () { + (await this.directory.address).should.not.be.null + }) + + it('has an stdlib', async function () { + (await this.directory.stdlib()).should.be.eq(this.stdlib.address) + }) + + it('has an owner', async function () { + (await this.directory.owner()).should.be.equal(appOwner) + }) + + it('can set new implementations', async function () { + const implementation = await DummyImplementation.new() + await this.directory.setImplementation('DummyImplementation', implementation.address) + + const currentImplementation = await this.directory.getImplementation('DummyImplementation') + currentImplementation.should.be.eq(implementation.address) + }) + + it('can retrieve an implementation from the stdlib if not registered', async function () { + let currentImplementation = await this.directory.getImplementation('DummyImplementation'); + currentImplementation.should.be.zeroAddress + + const implementation = await DummyImplementation.new() + await this.stdlib.setImplementation('DummyImplementation', implementation.address, { from: stdlibOwner }) + + currentImplementation = await this.directory.getImplementation('DummyImplementation') + currentImplementation.should.be.eq(implementation.address) + }) + + it('can set another stdlib', async function () { + const anotherStdlib = await ImplementationDirectory.new({ from: stdlibOwner }) + + await this.directory.setStdlib(anotherStdlib.address) + + const currentStdlib = await this.directory.stdlib(); + currentStdlib.should.be.eq(anotherStdlib.address) + }) +}) diff --git a/test/src/directory/FreezableImplementationDirectory.test.js b/test/src/directory/FreezableImplementationDirectory.test.js new file mode 100644 index 0000000..c35a33f --- /dev/null +++ b/test/src/directory/FreezableImplementationDirectory.test.js @@ -0,0 +1,87 @@ +'use strict' +require('../../setup') + +import Contracts from '../../../src/utils/Contracts' +import FreezableImplementationDirectory from '../../../src/directory/FreezableImplementationDirectory' + +const DummyImplementationV2 = Contracts.getFromLocal('DummyImplementationV2') + +contract('FreezableImplementationDirectory', ([_, owner]) => { + const txParams = { from: owner } + + describe('deployLocal', function () { + const contracts = [{ alias: 'DummyImplementation', name: 'DummyImplementation' }] + + beforeEach('deploying freezable implementation directory', async function () { + this.directory = await FreezableImplementationDirectory.deployLocal(contracts, txParams) + }) + + it('has an address', async function () { + (await this.directory.address).should.not.be.null + }) + + it('has an owner', async function () { + (await this.directory.owner()).should.be.equal(owner) + }) + + it('can be frozen', async function () { + let frozen = await this.directory.isFrozen(); + frozen.should.be.false + + await this.directory.freeze().should.eventually.be.fulfilled + + frozen = await this.directory.isFrozen() + frozen.should.be.true + }) + + it('includes the given contracts', async function () { + (await this.directory.getImplementation('DummyImplementation')).should.not.be.zero + }) + + it('can set new implementations', async function () { + const implementation = await DummyImplementationV2.new() + await this.directory.setImplementation('DummyImplementation', implementation.address) + + const currentImplementation = await this.directory.getImplementation('DummyImplementation'); + currentImplementation.should.be.eq(implementation.address) + }) + }) + + describe('deployDependency', function () { + const contracts = [{ alias: 'Greeter', name: 'Greeter' }] + + beforeEach('deploying freezable implementation directory', async function () { + this.directory = await FreezableImplementationDirectory.deployDependency('mock-dependency', contracts, txParams) + }) + + it('has an address', async function () { + (await this.directory.address).should.not.be.null + }) + + it('has an owner', async function () { + (await this.directory.owner()).should.be.equal(owner) + }) + + it('can be frozen', async function () { + let frozen = await this.directory.isFrozen(); + frozen.should.be.false + + await this.directory.freeze().should.eventually.be.fulfilled + + frozen = await this.directory.isFrozen() + frozen.should.be.true + }) + + it('includes the given contracts', async function () { + (await this.directory.getImplementation('Greeter')).should.not.be.zero + }) + + it('can set new implementations', async function () { + const implementation = await DummyImplementationV2.new() + await this.directory.setImplementation('DummyImplementation', implementation.address) + + const currentImplementation = await this.directory.getImplementation('DummyImplementation') + currentImplementation.should.be.eq(implementation.address) + }) + }) +}) diff --git a/test/src/directory/ImplementationDirectory.test.js b/test/src/directory/ImplementationDirectory.test.js new file mode 100644 index 0000000..72d950f --- /dev/null +++ b/test/src/directory/ImplementationDirectory.test.js @@ -0,0 +1,67 @@ +'use strict' +require('../../setup') + +import Contracts from '../../../src/utils/Contracts' +import ImplementationDirectory from '../../../src/directory/ImplementationDirectory' + +const DummyImplementationV2 = Contracts.getFromLocal('DummyImplementationV2') + +contract('ImplementationDirectory', ([_, owner, anotherAddress]) => { + const txParams = { from: owner } + + describe('deployLocal', function () { + const contracts = [{ alias: 'DummyImplementation', name: 'DummyImplementation' }] + + beforeEach('deploying implementation directory', async function () { + this.directory = await ImplementationDirectory.deployLocal(contracts, txParams) + }) + + it('has an address', async function () { + (await this.directory.address).should.not.be.null + }) + + it('has an owner', async function () { + (await this.directory.owner()).should.be.equal(owner) + }) + + it('includes the given contracts', async function () { + (await this.directory.getImplementation('DummyImplementation')).should.not.be.zero + }) + + it('can set new implementations', async function () { + const implementation = await DummyImplementationV2.new() + await this.directory.setImplementation('DummyImplementation', implementation.address) + + const currentImplementation = await this.directory.getImplementation('DummyImplementation'); + currentImplementation.should.be.eq(implementation.address) + }) + }) + + describe('deployDependency', function () { + const contracts = [{ alias: 'Greeter', name: 'Greeter' }] + + beforeEach('deploying implementation directory', async function () { + this.directory = await ImplementationDirectory.deployDependency('mock-dependency', contracts, txParams) + }) + + it('has an address', async function () { + (await this.directory.address).should.not.be.null + }) + + it('has an owner', async function () { + (await this.directory.owner()).should.be.equal(owner) + }) + + it('includes the given contracts', async function () { + (await this.directory.getImplementation('Greeter')).should.not.be.zero + }) + + it('can set new implementations', async function () { + const implementation = await DummyImplementationV2.new() + await this.directory.setImplementation('DummyImplementation', implementation.address) + + const currentImplementation = await this.directory.getImplementation('DummyImplementation'); + currentImplementation.should.be.eq(implementation.address) + }) + }) +}) diff --git a/test/src/package/Package.test.js b/test/src/package/Package.test.js deleted file mode 100644 index e6b4f0c..0000000 --- a/test/src/package/Package.test.js +++ /dev/null @@ -1,96 +0,0 @@ -'use strict' -require('../../setup') - -import Package from '../../../src/package/Package'; -import Contracts from '../../../src/utils/Contracts' -import assertRevert from '../../../src/test/helpers/assertRevert'; - -const DummyImplementation = Contracts.getFromLocal('DummyImplementation') - -contract('Package', function ([_, owner]) { - const txParams = { from: owner } - const contractName = 'DummyImplementation' - const initialVersion = "1.0" - const newVersion = "2.0" - - const shouldInitialize = function () { - it('instantiates the package', async function() { - this.package.address().should.not.be.null - }) - } - - const createRelease = async function () { - await this.package.newVersion(initialVersion) - } - - beforeEach("deploying", async function () { - this.package = await Package.deploy(txParams) - }) - - describe('deploy', function () { - shouldInitialize() - }) - - describe('connect', function () { - beforeEach("connecting to existing instance", async function () { - this.package = await Package.fetch(this.package.address(), txParams) - }) - - shouldInitialize() - }) - - describe('newVersion', function () { - beforeEach('creating a new release', createRelease) - - it('registers new version on package', async function () { - await this.package.newVersion(newVersion) - const hasVersion = await this.package.hasVersion(newVersion) - hasVersion.should.be.true - }) - }) - - describe('freeze', function() { - beforeEach('creating a new release', createRelease) - - it('should not be frozen by default', async function () { - const frozen = await this.package.isFrozen(initialVersion) - frozen.should.be.false - }) - - it('should be freezable', async function () { - await this.package.freeze(initialVersion) - const frozen = await this.package.isFrozen(initialVersion) - frozen.should.be.true - }) - }) - - describe('get and set implementation', function () { - beforeEach('creating a new release', createRelease) - - describe('while unfrozen', async function() { - beforeEach('setting implementation', async function() { - this.implementation = await this.package.setImplementation(initialVersion, DummyImplementation, contractName) - }) - - it('should return implementation', async function () { - const implementation = await this.package.getImplementation(initialVersion, contractName) - implementation.should.be.not.null - }) - - it('should register implementation on release version', async function () { - const implementation = await this.package.getImplementation(initialVersion, contractName) - implementation.should.eq(this.implementation.address) - }) - }) - - describe('while frozen', function() { - beforeEach('freezing', async function() { - await this.package.freeze(initialVersion) - }) - - it('should revert when registering an implementation', async function() { - await assertRevert(this.package.setImplementation(initialVersion, DummyImplementation, contractName)) - }) - }) - }) -}) diff --git a/test/src/package/PackageWithAppDirectories.test.js b/test/src/package/PackageWithAppDirectories.test.js new file mode 100644 index 0000000..8b1127b --- /dev/null +++ b/test/src/package/PackageWithAppDirectories.test.js @@ -0,0 +1,67 @@ +'use strict' +require('../../setup') + +import Contracts from '../../../src/utils/Contracts' +import PackageWithAppDirectories from '../../../src/package/PackageWithAppDirectories' + +const DummyImplementation = Contracts.getFromLocal('DummyImplementation') + +contract('PackageWithAppDirectories', function ([_, owner]) { + const txParams = { from: owner } + const contractName = 'DummyImplementation' + const version = "1.0" + + const shouldInitialize = function () { + it('instantiates the package', async function() { + this.package.address.should.not.be.null + }) + } + + beforeEach('deploying package with app directories', async function () { + this.package = await PackageWithAppDirectories.deploy(txParams) + }) + + describe('deploy', function () { + shouldInitialize() + }) + + describe('fetch', function () { + beforeEach("connecting to existing instance", async function () { + this.package = await PackageWithAppDirectories.fetch(this.package.address, txParams) + }) + + shouldInitialize() + }) + + const addNewVersion = async function () { + await this.package.newVersion(version) + } + + describe('newVersion', function () { + beforeEach('adding a new version', addNewVersion) + + it('registers new version on package', async function () { + const hasVersion = await this.package.hasVersion(version) + hasVersion.should.be.true + }) + }) + + describe('get and set implementation', function () { + beforeEach('adding a new version', addNewVersion) + + it('allows to register new implementations', async function() { + const newImplementation = await this.package.setImplementation(version, DummyImplementation, contractName) + + const implementation = await this.package.getImplementation(version, contractName) + implementation.should.eq(newImplementation.address) + }) + + it('allows to register the same implementations twice', async function() { + await this.package.setImplementation(version, DummyImplementation, contractName) + const newImplementation = await this.package.setImplementation(version, DummyImplementation, contractName) + + const implementation = await this.package.getImplementation(version, contractName) + implementation.should.eq(newImplementation.address) + }) + }) +}) diff --git a/test/src/package/PackageWithFreezableDirectories.test.js b/test/src/package/PackageWithFreezableDirectories.test.js new file mode 100644 index 0000000..1669c58 --- /dev/null +++ b/test/src/package/PackageWithFreezableDirectories.test.js @@ -0,0 +1,88 @@ +'use strict' +require('../../setup') + +import Contracts from '../../../src/utils/Contracts' +import assertRevert from '../../../src/test/helpers/assertRevert' +import PackageWithFreezableDirectories from '../../../src/package/PackageWithFreezableDirectories' + +const DummyImplementation = Contracts.getFromLocal('DummyImplementation') + +contract('PackageWithFreezableDirectories', function ([_, owner]) { + const txParams = { from: owner } + const contractName = 'DummyImplementation' + const version = "1.0" + + const shouldInitialize = function () { + it('instantiates the package', async function() { + this.package.address.should.not.be.null + }) + } + + beforeEach('deploying package with freezable directories', async function () { + this.package = await PackageWithFreezableDirectories.deploy(txParams) + }) + + describe('deploy', function () { + shouldInitialize() + }) + + describe('fetch', function () { + beforeEach("connecting to existing instance", async function () { + this.package = await PackageWithFreezableDirectories.fetch(this.package.address, txParams) + }) + + shouldInitialize() + }) + + const addNewVersion = async function () { + await this.package.newVersion(version) + } + + describe('newVersion', function () { + beforeEach('adding a new version', addNewVersion) + + it('registers new version on package', async function () { + const hasVersion = await this.package.hasVersion(version) + hasVersion.should.be.true + }) + }) + + describe('freeze', function() { + beforeEach('adding a new version', addNewVersion) + + it('should not be frozen by default', async function () { + const frozen = await this.package.isFrozen(version) + frozen.should.be.false + }) + + it('should be freezable', async function () { + await this.package.freeze(version) + const frozen = await this.package.isFrozen(version) + frozen.should.be.true + }) + }) + + describe('get and set implementation', function () { + beforeEach('adding a new version', addNewVersion) + + describe('when current version is not frozen', async function() { + + it('allows to register new implementations', async function() { + const newImplementation = await this.package.setImplementation(version, DummyImplementation, contractName) + + const implementation = await this.package.getImplementation(version, contractName) + implementation.should.eq(newImplementation.address) + }) + }) + + describe('when current version is frozen', async function() { + beforeEach('freezing', async function() { + await this.package.freeze(version) + }) + + it('does not allow to register new implementations', async function() { + await assertRevert(this.package.setImplementation(version, DummyImplementation, contractName)) + }) + }) + }) +}) diff --git a/test/src/package/PackageWithNonFreezableDirectories.test.js b/test/src/package/PackageWithNonFreezableDirectories.test.js new file mode 100644 index 0000000..41eac1b --- /dev/null +++ b/test/src/package/PackageWithNonFreezableDirectories.test.js @@ -0,0 +1,67 @@ +'use strict' +require('../../setup') + +import Contracts from '../../../src/utils/Contracts' +import PackageWithNonFreezableDirectories from '../../../src/package/PackageWithNonFreezableDirectories' + +const DummyImplementation = Contracts.getFromLocal('DummyImplementation') + +contract('PackageWithNonFreezableDirectories', function ([_, owner]) { + const txParams = { from: owner } + const contractName = 'DummyImplementation' + const version = "1.0" + + const shouldInitialize = function () { + it('instantiates the package', async function() { + this.package.address.should.not.be.null + }) + } + + beforeEach('deploying package with non-freezable directories', async function () { + this.package = await PackageWithNonFreezableDirectories.deploy(txParams) + }) + + describe('deploy', function () { + shouldInitialize() + }) + + describe('fetch', function () { + beforeEach("connecting to existing instance", async function () { + this.package = await PackageWithNonFreezableDirectories.fetch(this.package.address, txParams) + }) + + shouldInitialize() + }) + + const addNewVersion = async function () { + await this.package.newVersion(version) + } + + describe('newVersion', function () { + beforeEach('adding a new version', addNewVersion) + + it('registers new version on package', async function () { + const hasVersion = await this.package.hasVersion(version) + hasVersion.should.be.true + }) + }) + + describe('get and set implementation', function () { + beforeEach('adding a new version', addNewVersion) + + it('allows to register new implementations', async function() { + const newImplementation = await this.package.setImplementation(version, DummyImplementation, contractName) + + const implementation = await this.package.getImplementation(version, contractName) + implementation.should.eq(newImplementation.address) + }) + + it('allows to register the same implementations twice', async function() { + await this.package.setImplementation(version, DummyImplementation, contractName) + const newImplementation = await this.package.setImplementation(version, DummyImplementation, contractName) + + const implementation = await this.package.getImplementation(version, contractName) + implementation.should.eq(newImplementation.address) + }) + }) +}) diff --git a/test/src/release/Release.test.js b/test/src/release/Release.test.js deleted file mode 100644 index 4fe9310..0000000 --- a/test/src/release/Release.test.js +++ /dev/null @@ -1,78 +0,0 @@ -'use strict' -require('../../setup') - -import Release from '../../../src/release/Release' - -contract('Release', ([_, owner]) => { - const txParams = { from: owner } - - describe('deployLocal', function () { - const contracts = [{ alias: 'DummyImplementation', name: 'DummyImplementation' }] - - beforeEach(async function () { - this.release = await Release.deployLocal(contracts, txParams) - }) - - it('has an owner', async function () { - (await this.release.owner()).should.be.equal(owner) - }) - - it('can be frozen', async function () { - let frozen = await this.release.isFrozen(); - frozen.should.be.false - - await this.release.freeze().should.eventually.be.fulfilled - - frozen = await this.release.isFrozen() - frozen.should.be.true - }) - - it('can tell the implementation of a contract', async function () { - (await this.release.getImplementation('DummyImplementation')).should.not.be.zero - }) - - it('deploys a new release', async function () { - this.release.address().should.be.not.null; - (await this.release.owner()).should.be.equal(owner) - }) - - it('includes the given contracts', async function () { - (await this.release.getImplementation('DummyImplementation')).should.not.be.zero - }) - }) - - describe('deployDependency', function () { - const contracts = [{ alias: 'Greeter', name: 'Greeter' }] - - beforeEach(async function () { - this.release = await Release.deployDependency('mock-dependency', contracts, txParams) - }) - - it('has an owner', async function () { - (await this.release.owner()).should.be.equal(owner) - }) - - it('can be frozen', async function () { - let frozen = await this.release.isFrozen(); - frozen.should.be.false - - await this.release.freeze().should.eventually.be.fulfilled - - frozen = await this.release.isFrozen() - frozen.should.be.true - }) - - it('can tell the implementation of a contract', async function () { - (await this.release.getImplementation('Greeter')).should.not.be.zero - }) - - it('deploys a new release', async function () { - this.release.address().should.be.not.null; - (await this.release.owner()).should.be.equal(owner) - }) - - it('includes the given contracts', async function () { - (await this.release.getImplementation('Greeter')).should.not.be.zero - }) - }) -})