@0x/contracts-zero-ex: bootstrap() is now a temporary feature, registered in the ZeroEx constructor.

`@0x/contracts-zero-ex`: `bootstrap()` de-registers itself and self-destructs once it's called.
`@0x/contracts-zero-ex`: `bootstrap()` now takes arbitrary call data, but the callee is fixed in an immutable.
`@0x/contracts-zero-ex`: `bootstrap()` caller is fixed in an immutable.
`@0x/contracts-zero-ex`: `bootstrap()` only calls a single target.
`@0x/contracts-zero-ex`: Renamed `BasicMigration` to `InitialMigration`.
`@0x/contracts-zero-ex`: `InitialMigration` is now the bootstrap target and multiplexes to the initial features.
`@0x/contracts-zero-ex`: Add `Migrate` feature and tests.
`@0x/contracts-zero-ex`: Re-organize contract locatins (remove `interfaces` folder).
This commit is contained in:
Lawrence Forman 2020-04-16 03:12:53 -04:00
parent 0c33aa16a1
commit 12f2250ab5
33 changed files with 870 additions and 280 deletions

View File

@ -20,7 +20,8 @@ pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
import "./interfaces/IZeroExBootstrapper.sol";
import "./migrations/LibBootstrap.sol";
import "./features/Bootstrap.sol";
import "./storage/LibProxyStorage.sol";
import "./errors/LibProxyRichErrors.sol";
@ -28,17 +29,23 @@ import "./errors/LibProxyRichErrors.sol";
/// @dev An extensible proxy contract that serves as a universal entry point for
/// interacting with the 0x protocol.
contract ZeroEx {
// solhint-disable separate-by-one-line-in-contract,indent,var-name-mixedcase
using LibBytesV06 for bytes;
/// @dev Construct this contract.
/// After constructing this contract, the deployer should call
/// `bootstrap()` to seed the initial feature set.
constructor() public {
// Set the `bootstrap()` caller to the deployer.
LibProxyStorage.getStorage().bootstrapCaller = msg.sender;
/// @dev Magic bytes returned by the bootstrapper to indicate success.
bytes4 internal constant BOOTSTRAP_SUCCESS = 0xd150751b;
/// @dev Construct this contract and set the bootstrap migration contract.
/// After constructing this contract, `bootstrap()` should be called
/// to seed the initial feature set.
/// @param bootstrapper The bootstrap migration contract.
constructor(address bootstrapper) public {
// Temporarily create and register the bootstrap feature.
// It will deregister itself after `bootstrap()` has been called.
Bootstrap bootstrap = new Bootstrap(msg.sender, bootstrapper);
LibProxyStorage.getStorage().impls[bootstrap.bootstrap.selector] =
address(bootstrap);
}
// solhint-disable state-visibility
@ -63,40 +70,6 @@ contract ZeroEx {
// solhint-enable state-visibility
/// @dev Bootstrap the initial feature set of this contract.
/// This can only be called once by the deployer of this contract.
/// @param bootstrappers Array of bootstrapping contracts to delegatecall into.
function bootstrap(IZeroExBootstrapper[] calldata bootstrappers) external {
LibProxyStorage.Storage storage stor = LibProxyStorage.getStorage();
// If `bootstrapCaller` is zero, the contract has already been bootstrapped.
address bootstrapCaller = stor.bootstrapCaller;
if (bootstrapCaller == address(0)) {
_revertWithData(LibProxyRichErrors.AlreadyBootstrappedError());
}
// Only the deployer caller can call this function.
if (bootstrapCaller != msg.sender) {
_revertWithData(
LibProxyRichErrors.InvalidBootstrapCallerError(msg.sender, bootstrapCaller)
);
}
// Prevent calling `bootstrap()` again.
stor.bootstrapCaller = address(0);
// Call the bootstrap contracts.
for (uint256 i = 0; i < bootstrappers.length; ++i) {
// Delegatecall into the bootstrap contract.
(bool success, bytes memory resultData) = address(bootstrappers[i])
.delegatecall(abi.encodeWithSelector(
IZeroExBootstrapper.bootstrap.selector,
address(bootstrappers[i])
));
if (!success) {
_revertWithData(resultData);
}
}
}
/// @dev Get the implementation contract of a registered function.
/// @param selector The function selector.
/// @return impl The implementation contract address.

View File

@ -17,17 +17,31 @@
*/
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../src/migrations/BasicMigration.sol";
import "../src/interfaces/IZeroExBootstrapper.sol";
contract TestBasicMigration is
BasicMigration
library LibMigrateRichErrors {
// solhint-disable func-name-mixedcase
function AlreadyMigratingError()
internal
pure
returns (bytes memory)
{
function callBootstrap(ZeroEx zeroEx) external {
IZeroExBootstrapper[] memory bootstrappers;
zeroEx.bootstrap(bootstrappers);
return abi.encodeWithSelector(
bytes4(keccak256("AlreadyMigratingError()"))
);
}
function MigrateCallFailedError(address target, bytes memory resultData)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("MigrateCallFailedError(address,bytes)")),
target,
resultData
);
}
}

View File

@ -34,25 +34,39 @@ library LibProxyRichErrors {
);
}
function AlreadyBootstrappedError()
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("AlreadyBootstrappedError()"))
);
}
function InvalidBootstrapCallerError(address caller, address expectedCaller)
function InvalidBootstrapCallerError(address actual, address expected)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("InvalidBootstrapCallerError(address,address)")),
caller,
expectedCaller
actual,
expected
);
}
function InvalidDieCallerError(address actual, address expected)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("InvalidDieCallerError(address,address)")),
actual,
expected
);
}
function BootstrapCallFailedError(address target, bytes memory resultData)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("BootstrapCallFailedError(address,bytes)")),
target,
resultData
);
}
}

View File

@ -0,0 +1,87 @@
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../migrations/LibBootstrap.sol";
import "../fixins/FixinCommon.sol";
import "../storage/LibProxyStorage.sol";
import "./IBootstrap.sol";
/// @dev Detachable `bootstrap()` feature.
contract Bootstrap is
IBootstrap,
FixinCommon
{
// solhint-disable state-visibility,indent
/// @dev The ZeroEx contract.
/// This has to be immutable to persist across delegatecalls.
address immutable private _deployer;
/// @dev The implementation address of this contract.
/// This has to be immutable to persist across delegatecalls.
address immutable private _implementation;
/// @dev The deployer.
/// This has to be immutable to persist across delegatecalls.
address immutable private _bootstrapCaller;
/// @dev The bootstrap migrator.
/// This has to be immutable to persist across delegatecalls.
address immutable private _bootstrapper;
// solhint-enable state-visibility,indent
/// @dev Construct this contract and set the bootstrap migration contract.
/// After constructing this contract, `bootstrap()` should be called
/// to seed the initial feature set.
/// @param bootstrapCaller The allowed caller of `bootstrap()`.
/// @param bootstrapper The bootstrap migration contract.
constructor(address bootstrapCaller, address bootstrapper) public {
_deployer = msg.sender;
_implementation = address(this);
_bootstrapCaller = bootstrapCaller;
_bootstrapper = bootstrapper;
}
/// @dev Bootstrap the initial feature set of this contract by delegatecalling
/// into `_bootstrapper`. Before exiting the `bootstrap()` function will
/// deregister itself from the proxy to prevent being called again.
/// @param callData The call data to execute on `_bootstrapper`.
function bootstrap(bytes calldata callData) external override {
// Only the bootstrap caller can call this function.
if (msg.sender != _bootstrapCaller) {
_rrevert(LibProxyRichErrors.InvalidBootstrapCallerError(
msg.sender,
_bootstrapCaller
));
}
LibBootstrap.delegatecallBootstrapFunction(_bootstrapper, callData);
// Deregister.
LibProxyStorage.getStorage().impls[this.bootstrap.selector] = address(0);
// Self-destruct.
Bootstrap(_implementation).die();
}
/// @dev Self-destructs this contract.
/// Can only be called by the ZeroEx contract.
function die() external {
if (msg.sender != _deployer) {
_rrevert(LibProxyRichErrors.InvalidDieCallerError(msg.sender, _deployer));
}
selfdestruct(msg.sender);
}
}

View File

@ -20,13 +20,12 @@ pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
/// @dev Interface for a bootstrapping contract that the `ZeroEx` proxy.
interface IZeroExBootstrapper {
/// @dev Detachable `bootstrap()` feature.
interface IBootstrap {
/// @dev Sets up the initial state of the `ZeroEx` contract.
/// The `ZeroEx` contract will delegatecall this function so the
/// bootstrapper should use this function to register initial
/// features.
/// @param impl The implementation contract.
function bootstrap(address impl) external;
/// @dev Bootstrap the initial feature set of this contract by delegatecalling
/// into `_bootstrapper`. Before exiting the `bootstrap()` function will
/// deregister itself from the proxy to prevent being called again.
/// @param callData The call data to execute on `_bootstrapper`.
function bootstrap(bytes calldata callData) external;
}

View File

@ -0,0 +1,43 @@
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
/// @dev Migration features.
interface IMigrate {
/// @dev Emitted when `migrate()` is called.
/// @param caller The caller of `migrate()`.
/// @param migrator The migration contract.
event Migrated(address caller, address migrator);
/// @dev Execute a migration function in the context of the ZeroEx contract.
/// The result of the function being called should be the magic bytes
/// 0x2c64c5ef. Only callable by the owner.
/// The owner will be temporarily set to `address(this)` inside the call.
/// The original owner can be retrieved through `getMigrationOwner()`.`
/// @param target The migrator contract address.
/// @param data The call data.
function migrate(address target, bytes calldata data) external;
/// @dev Get the true owner of this contract during a migration.
/// @return owner The true owner of this contract.
function getMigrationOwner() external view returns (address owner);
}

View File

@ -19,8 +19,6 @@
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../interfaces/IZeroExBootstrapper.sol";
/// @dev Basic registry management features.
interface ISimpleFunctionRegistry {
@ -41,12 +39,6 @@ interface ISimpleFunctionRegistry {
/// @param impl The implementation contract for the function.
function extend(bytes4 selector, address impl) external;
/// @dev Register or replace a function.
/// Only callable from within.
/// @param selector The function selector.
/// @param impl The implementation contract for the function.
function extendSelf(bytes4 selector, address impl) external;
/// @dev Retrieve the length of the rollback history for a function.
/// @param selector The function selector.
/// @return rollbackLength The number of items in the rollback history for

View File

@ -0,0 +1,94 @@
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../fixins/FixinOwnable.sol";
import "../errors/LibOwnableRichErrors.sol";
import "../storage/LibOwnableStorage.sol";
import "../storage/LibMigrateStorage.sol";
import "../migrations/LibMigrate.sol";
import "../migrations/LibBootstrap.sol";
import "./IFeature.sol";
import "./IMigrate.sol";
import "./ISimpleFunctionRegistry.sol";
/// @dev Migration features.
contract Migrate is
IFeature,
IMigrate,
FixinOwnable
{
// solhint-disable const-name-snakecase
/// @dev Name of this feature.
string constant public override FEATURE_NAME = "Migrate";
/// @dev Version of this feature.
uint256 constant public override FEATURE_VERSION = (1 << 64) | (0 << 32) | (0);
/// @dev Initializes this feature.
/// @param impl The actual address of this feature contract.
/// @return success Magic bytes if successful.
function bootstrap(address impl) external returns (bytes4 success) {
// Register feature functions.
ISimpleFunctionRegistry(address(this)).extend(this.migrate.selector, impl);
ISimpleFunctionRegistry(address(this)).extend(this.getMigrationOwner.selector, impl);
return LibBootstrap.BOOTSTRAP_SUCCESS;
}
/// @dev Execute a migration function in the context of the ZeroEx contract.
/// The result of the function being called should be the magic bytes
/// 0x2c64c5ef. Only callable by the owner.
/// The owner will be temporarily set to `address(this)` inside the call.
/// The original owner can be retrieved through `getMigrationOwner()`.`
/// @param target The migrator contract address.
/// @param data The call data.
function migrate(address target, bytes calldata data)
external
override
onlyOwner
{
LibOwnableStorage.Storage storage ownableStor = LibOwnableStorage.getStorage();
LibMigrateStorage.Storage storage stor = LibMigrateStorage.getStorage();
address prevOwner = ownableStor.owner;
if (prevOwner == address(this)) {
// If the owner is already set to ourselves then we've reentered.
_rrevert(LibMigrateRichErrors.AlreadyMigratingError());
}
// Temporarily set the owner to ourselves.
ownableStor.owner = address(this);
stor.migrationOwner = prevOwner;
// Perform the migration.
LibMigrate.delegatecallMigrateFunction(target, data);
// Restore the owner.
ownableStor.owner = prevOwner;
stor.migrationOwner = address(0);
emit Migrated(msg.sender, target);
}
/// @dev Get the true owner of this contract during a migration.
/// @return owner The true owner of this contract.
function getMigrationOwner() external override view returns (address owner) {
return LibMigrateStorage.getStorage().migrationOwner;
}
}

View File

@ -20,19 +20,18 @@ pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../fixins/FixinOwnable.sol";
import "../interfaces/IFeature.sol";
import "../interfaces/IOwnable.sol";
import "../interfaces/ISimpleFunctionRegistry.sol";
import "../interfaces/IZeroExBootstrapper.sol";
import "../errors/LibOwnableRichErrors.sol";
import "../storage/LibOwnableStorage.sol";
import "../migrations/LibBootstrap.sol";
import "./IFeature.sol";
import "./IOwnable.sol";
import "./ISimpleFunctionRegistry.sol";
/// @dev Owner management features.
contract Ownable is
IFeature,
IOwnable,
IZeroExBootstrapper,
FixinOwnable
{
// solhint-disable const-name-snakecase
@ -42,15 +41,20 @@ contract Ownable is
/// @dev Version of this feature.
uint256 constant public override FEATURE_VERSION = (1 << 64) | (0 << 32) | (0);
/// @dev Initializes the authority feature.
/// @param impl The actual address of this feature contract.
function bootstrap(address impl) external override {
/// @dev Initializes this feature. The intial owner will be set to this (ZeroEx)
/// to allow the bootstrappers to call `extend()`. Ownership should be
/// transferred to the real owner by the bootstrapper after
/// bootstrapping is complete.
/// @param impl the actual address of this feature contract.
/// @return success Magic bytes if successful.
function bootstrap(address impl) external returns (bytes4 success) {
// Set the owner.
LibOwnableStorage.getStorage().owner = msg.sender;
LibOwnableStorage.getStorage().owner = address(this);
// Register feature functions.
ISimpleFunctionRegistry(address(this)).extendSelf(this.transferOwnership.selector, impl);
ISimpleFunctionRegistry(address(this)).extendSelf(this.getOwner.selector, impl);
ISimpleFunctionRegistry(address(this)).extend(this.transferOwnership.selector, impl);
ISimpleFunctionRegistry(address(this)).extend(this.getOwner.selector, impl);
return LibBootstrap.BOOTSTRAP_SUCCESS;
}
/// @dev Change the owner of this contract.

View File

@ -19,20 +19,19 @@
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../interfaces/IFeature.sol";
import "../interfaces/ISimpleFunctionRegistry.sol";
import "../interfaces/IZeroExBootstrapper.sol";
import "../fixins/FixinOwnable.sol";
import "../storage/LibProxyStorage.sol";
import "../storage/LibSimpleFunctionRegistryStorage.sol";
import "../errors/LibSimpleFunctionRegistryRichErrors.sol";
import "../migrations/LibBootstrap.sol";
import "./IFeature.sol";
import "./ISimpleFunctionRegistry.sol";
/// @dev Basic registry management features.
contract SimpleFunctionRegistry is
IFeature,
ISimpleFunctionRegistry,
IZeroExBootstrapper,
FixinOwnable
{
// solhint-disable const-name-snakecase
@ -42,17 +41,18 @@ contract SimpleFunctionRegistry is
/// @dev Version of this feature.
uint256 constant public override FEATURE_VERSION = (1 << 64) | (0 << 32) | (0);
/// @dev Initializes the feature implementation registry.
/// @dev Initializes this feature.
/// @param impl The actual address of this feature contract.
function bootstrap(address impl) external override {
/// @return success Magic bytes if successful.
function bootstrap(address impl) external returns (bytes4 success) {
// Register the registration functions (inception vibes).
_extend(this.extend.selector, impl);
_extend(this.extendSelf.selector, impl);
// Register the rollback function.
_extend(this.rollback.selector, impl);
// Register getters.
_extend(this.getRollbackLength.selector, impl);
_extend(this.getRollbackEntryAtIndex.selector, impl);
return LibBootstrap.BOOTSTRAP_SUCCESS;
}
/// @dev Roll back to a prior implementation of a function.
@ -108,18 +108,6 @@ contract SimpleFunctionRegistry is
_extend(selector, impl);
}
/// @dev Register or replace a function.
/// Only callable from within.
/// @param selector The function selector.
/// @param impl The implementation contract for the function.
function extendSelf(bytes4 selector, address impl)
external
override
onlySelf
{
_extend(selector, impl);
}
/// @dev Retrieve the length of the rollback history for a function.
/// @param selector The function selector.
/// @return rollbackLength The number of items in the rollback history for

View File

@ -1,70 +0,0 @@
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../ZeroEx.sol";
import "../features/SimpleFunctionRegistry.sol";
import "../features/Ownable.sol";
import "../interfaces/IOwnable.sol";
import "../interfaces/IZeroExBootstrapper.sol";
import "../interfaces/ISimpleFunctionRegistry.sol";
/// @dev A contract for deploying and configuring a minimal ZeroEx contract.
contract BasicMigration {
/// @dev Deploy the `ZeroEx` contract with the minimum feature set.
/// @param owner The owner of the contract.
/// @return zeroEx The deployed and configured `ZeroEx` contract.
function migrate(address owner) external returns (ZeroEx zeroEx) {
// Deploy the ZeroEx contract.
zeroEx = new ZeroEx();
// Bootstrap the initial feature set.
zeroEx.bootstrap(_createBootstrappers());
// Disable the `extendSelf()` function by rolling it back to zero.
ISimpleFunctionRegistry(address(zeroEx))
.rollback(ISimpleFunctionRegistry.extendSelf.selector, address(0));
// Call the _postInitialize hook.
_postInitialize(zeroEx);
// Transfer ownership.
if (owner != address(this)) {
IOwnable(address(zeroEx)).transferOwnership(owner);
}
}
function _createBootstrappers()
internal
virtual
returns (IZeroExBootstrapper[] memory bootstrappers)
{
bootstrappers = new IZeroExBootstrapper[](2);
bootstrappers[0] = IZeroExBootstrapper(address(new SimpleFunctionRegistry()));
bootstrappers[1] = IZeroExBootstrapper(address(new Ownable()));
}
// solhint-disable no-empty-blocks
function _postInitialize(ZeroEx zeroEx) internal virtual {
// Override me.
}
}

View File

@ -0,0 +1,87 @@
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../ZeroEx.sol";
import "../features/IBootstrap.sol";
import "../features/SimpleFunctionRegistry.sol";
import "../features/Ownable.sol";
import "../features/Migrate.sol";
import "./LibBootstrap.sol";
/// @dev A contract for deploying and configuring a minimal ZeroEx contract.
contract InitialMigration {
/// @dev The deployed ZereEx instance.
ZeroEx private _zeroEx;
/// @dev Deploy the `ZeroEx` contract with the minimum feature set,
/// transfers ownership to `owner`, then self-destructs.
/// @param owner The owner of the contract.
/// @return zeroEx The deployed and configured `ZeroEx` contract.
function deploy(address owner) public virtual returns (ZeroEx zeroEx) {
// Must not have already been run.
require(address(_zeroEx) == address(0), "InitialMigration/ALREADY_DEPLOYED");
// Deploy the ZeroEx contract, setting ourselves as the bootstrapper.
zeroEx = _zeroEx = new ZeroEx(address(this));
// Bootstrap the initial feature set.
IBootstrap(address(zeroEx)).bootstrap(abi.encodeWithSelector(
this.bootstrap.selector,
owner
));
}
/// @dev Sets up the initial state of the `ZeroEx` contract.
/// The `ZeroEx` contract will delegatecall into this function.
/// @param owner The new owner of the ZeroEx contract.
/// @return success Magic bytes if successful.
function bootstrap(address owner) public virtual returns (bytes4 success) {
// Deploy and migrate the initial features.
// Order matters here.
// Initialize Registry.
SimpleFunctionRegistry registry = new SimpleFunctionRegistry();
LibBootstrap.delegatecallBootstrapFunction(
address(registry),
abi.encodeWithSelector(registry.bootstrap.selector, address(registry))
);
// Initialize Ownable.
Ownable ownable = new Ownable();
LibBootstrap.delegatecallBootstrapFunction(
address(ownable),
abi.encodeWithSelector(ownable.bootstrap.selector, address(ownable))
);
// Initialize Migrate.
Migrate migrate = new Migrate();
LibBootstrap.delegatecallBootstrapFunction(
address(migrate),
abi.encodeWithSelector(migrate.bootstrap.selector, address(migrate))
);
// Transfer ownership to the real owner.
Ownable(address(this)).transferOwnership(owner);
success = LibBootstrap.BOOTSTRAP_SUCCESS;
}
}

View File

@ -0,0 +1,50 @@
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
import "../errors/LibProxyRichErrors.sol";
library LibBootstrap {
/// @dev Magic bytes returned by the bootstrapper to indicate success.
bytes4 internal constant BOOTSTRAP_SUCCESS = 0xd150751b;
/// @dev Perform a delegatecall and ensure it returns the magic bytes.
/// @param target The call target.
/// @param data The call data.
function delegatecallBootstrapFunction(
address target,
bytes memory data
)
internal
{
(bool success, bytes memory resultData) = target.delegatecall(data);
if (!success ||
resultData.length != 32 ||
abi.decode(resultData, (bytes4)) != BOOTSTRAP_SUCCESS)
{
LibRichErrorsV06.rrevert(
LibProxyRichErrors.BootstrapCallFailedError(target, resultData)
);
}
}
}

View File

@ -0,0 +1,50 @@
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
import "../errors/LibMigrateRichErrors.sol";
library LibMigrate {
/// @dev Magic bytes returned by a migrator to indicate success.
bytes4 internal constant MIGRATE_SUCCESS = 0x2c64c5ef;
/// @dev Perform a delegatecall and ensure it returns the magic bytes.
/// @param target The call target.
/// @param data The call data.
function delegatecallMigrateFunction(
address target,
bytes memory data
)
internal
{
(bool success, bytes memory resultData) = target.delegatecall(data);
if (!success ||
resultData.length != 32 ||
abi.decode(resultData, (bytes4)) != MIGRATE_SUCCESS)
{
LibRichErrorsV06.rrevert(
LibMigrateRichErrors.MigrateCallFailedError(target, resultData)
);
}
}
}

View File

@ -0,0 +1,41 @@
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
/// @dev Storage helpers for the `Migrate` feature.
library LibMigrateStorage {
/// @dev Globally unique offset for the storage bucket.
bytes32 constant internal STORAGE_ID =
0x0ed67b719caa0e9bebb7147a4de9fdb6f1c82a984b2297d741a9888432214d5c;
/// @dev Storage bucket for this feature.
struct Storage {
// The owner of this contract prior to the `migrate()` call.
address migrationOwner;
}
/// @dev Get the storage bucket for this contract.
function getStorage() internal pure returns (Storage storage stor) {
bytes32 storageId = STORAGE_ID;
assembly { stor_slot := storageId }
}
}

View File

@ -29,8 +29,6 @@ library LibProxyStorage {
/// @dev Storage bucket for proxy contract.
struct Storage {
// The allowed caller for `bootstrap()`.
address bootstrapCaller;
// Mapping of function selector -> function implementation
mapping(bytes4 => address) impls;
}

View File

@ -0,0 +1,46 @@
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../src/ZeroEx.sol";
import "../src/features/IBootstrap.sol";
import "../src/migrations/InitialMigration.sol";
contract TestInitialMigration is
InitialMigration
{
address public bootstrapFeature;
function callBootstrap(ZeroEx zeroEx) external {
IBootstrap(address(zeroEx)).bootstrap(new bytes(0));
}
function getCodeSizeOf(address target) external view returns (uint256 codeSize) {
assembly { codeSize := extcodesize(target) }
}
function bootstrap(address owner) public override returns (bytes4 success) {
success = InitialMigration.bootstrap(owner);
// Snoop the bootstrap feature contract.
bootstrapFeature = ZeroEx(address(uint160(address(this))))
.getFunctionImplementation(IBootstrap.bootstrap.selector);
}
}

View File

@ -0,0 +1,55 @@
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../src/migrations/LibMigrate.sol";
import "../src/features/IOwnable.sol";
import "../src/features/IMigrate.sol";
contract TestMigrator {
event TestMigrateCalled(
bytes callData,
address owner,
address actualOwner
);
function succeedingMigrate() external returns (bytes4 success) {
emit TestMigrateCalled(
msg.data,
IOwnable(address(this)).getOwner(),
IMigrate(address(this)).getMigrationOwner()
);
return LibMigrate.MIGRATE_SUCCESS;
}
function failingMigrate() external returns (bytes4 success) {
emit TestMigrateCalled(
msg.data,
IOwnable(address(this)).getOwner(),
IMigrate(address(this)).getMigrationOwner()
);
return 0xdeadbeef;
}
function revertingMigrate() external pure {
revert("OOPSIE");
}
}

View File

@ -38,9 +38,9 @@
"docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
},
"config": {
"publicInterfaceContracts": "ZeroEx,IOwnable,ISimpleFunctionRegistry",
"publicInterfaceContracts": "ZeroEx,IMigrate,IOwnable,ISimpleFunctionRegistry",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(BasicMigration|FixinCommon|FixinOwnable|IFeature|IOwnable|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|IZeroExBootstrapper|LibCommonRichErrors|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|Ownable|SimpleFunctionRegistry|TestBasicMigration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestZeroExFeature|ZeroEx).json"
"abis": "./test/generated-artifacts/@(Bootstrap|FixinCommon|FixinOwnable|IBootstrap|IFeature|IMigrate|IOwnable|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|InitialMigration|LibBootstrap|LibCommonRichErrors|LibMigrate|LibMigrateRichErrors|LibMigrateStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|Migrate|Ownable|SimpleFunctionRegistry|TestInitialMigration|TestMigrator|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestZeroExFeature|ZeroEx).json"
},
"repository": {
"type": "git",

View File

@ -5,11 +5,13 @@
*/
import { ContractArtifact } from 'ethereum-types';
import * as IMigrate from '../generated-artifacts/IMigrate.json';
import * as IOwnable from '../generated-artifacts/IOwnable.json';
import * as ISimpleFunctionRegistry from '../generated-artifacts/ISimpleFunctionRegistry.json';
import * as ZeroEx from '../generated-artifacts/ZeroEx.json';
export const artifacts = {
ZeroEx: ZeroEx as ContractArtifact,
IMigrate: IMigrate as ContractArtifact,
IOwnable: IOwnable as ContractArtifact,
ISimpleFunctionRegistry: ISimpleFunctionRegistry as ContractArtifact,
};

View File

@ -3,6 +3,7 @@
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
* -----------------------------------------------------------------------------
*/
export * from '../generated-wrappers/i_migrate';
export * from '../generated-wrappers/i_ownable';
export * from '../generated-wrappers/i_simple_function_registry';
export * from '../generated-wrappers/zero_ex';

View File

@ -5,24 +5,32 @@
*/
import { ContractArtifact } from 'ethereum-types';
import * as BasicMigration from '../test/generated-artifacts/BasicMigration.json';
import * as Bootstrap from '../test/generated-artifacts/Bootstrap.json';
import * as FixinCommon from '../test/generated-artifacts/FixinCommon.json';
import * as FixinOwnable from '../test/generated-artifacts/FixinOwnable.json';
import * as IBootstrap from '../test/generated-artifacts/IBootstrap.json';
import * as IFeature from '../test/generated-artifacts/IFeature.json';
import * as IMigrate from '../test/generated-artifacts/IMigrate.json';
import * as InitialMigration from '../test/generated-artifacts/InitialMigration.json';
import * as IOwnable from '../test/generated-artifacts/IOwnable.json';
import * as ISimpleFunctionRegistry from '../test/generated-artifacts/ISimpleFunctionRegistry.json';
import * as ITestSimpleFunctionRegistryFeature from '../test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json';
import * as IZeroExBootstrapper from '../test/generated-artifacts/IZeroExBootstrapper.json';
import * as LibBootstrap from '../test/generated-artifacts/LibBootstrap.json';
import * as LibCommonRichErrors from '../test/generated-artifacts/LibCommonRichErrors.json';
import * as LibMigrate from '../test/generated-artifacts/LibMigrate.json';
import * as LibMigrateRichErrors from '../test/generated-artifacts/LibMigrateRichErrors.json';
import * as LibMigrateStorage from '../test/generated-artifacts/LibMigrateStorage.json';
import * as LibOwnableRichErrors from '../test/generated-artifacts/LibOwnableRichErrors.json';
import * as LibOwnableStorage from '../test/generated-artifacts/LibOwnableStorage.json';
import * as LibProxyRichErrors from '../test/generated-artifacts/LibProxyRichErrors.json';
import * as LibProxyStorage from '../test/generated-artifacts/LibProxyStorage.json';
import * as LibSimpleFunctionRegistryRichErrors from '../test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json';
import * as LibSimpleFunctionRegistryStorage from '../test/generated-artifacts/LibSimpleFunctionRegistryStorage.json';
import * as Migrate from '../test/generated-artifacts/Migrate.json';
import * as Ownable from '../test/generated-artifacts/Ownable.json';
import * as SimpleFunctionRegistry from '../test/generated-artifacts/SimpleFunctionRegistry.json';
import * as TestBasicMigration from '../test/generated-artifacts/TestBasicMigration.json';
import * as TestInitialMigration from '../test/generated-artifacts/TestInitialMigration.json';
import * as TestMigrator from '../test/generated-artifacts/TestMigrator.json';
import * as TestSimpleFunctionRegistryFeatureImpl1 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl1.json';
import * as TestSimpleFunctionRegistryFeatureImpl2 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl2.json';
import * as TestZeroExFeature from '../test/generated-artifacts/TestZeroExFeature.json';
@ -30,23 +38,31 @@ import * as ZeroEx from '../test/generated-artifacts/ZeroEx.json';
export const artifacts = {
ZeroEx: ZeroEx as ContractArtifact,
LibCommonRichErrors: LibCommonRichErrors as ContractArtifact,
LibMigrateRichErrors: LibMigrateRichErrors as ContractArtifact,
LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact,
LibProxyRichErrors: LibProxyRichErrors as ContractArtifact,
LibSimpleFunctionRegistryRichErrors: LibSimpleFunctionRegistryRichErrors as ContractArtifact,
Bootstrap: Bootstrap as ContractArtifact,
IBootstrap: IBootstrap as ContractArtifact,
IFeature: IFeature as ContractArtifact,
IMigrate: IMigrate as ContractArtifact,
IOwnable: IOwnable as ContractArtifact,
ISimpleFunctionRegistry: ISimpleFunctionRegistry as ContractArtifact,
Migrate: Migrate as ContractArtifact,
Ownable: Ownable as ContractArtifact,
SimpleFunctionRegistry: SimpleFunctionRegistry as ContractArtifact,
FixinCommon: FixinCommon as ContractArtifact,
FixinOwnable: FixinOwnable as ContractArtifact,
IFeature: IFeature as ContractArtifact,
IOwnable: IOwnable as ContractArtifact,
ISimpleFunctionRegistry: ISimpleFunctionRegistry as ContractArtifact,
IZeroExBootstrapper: IZeroExBootstrapper as ContractArtifact,
BasicMigration: BasicMigration as ContractArtifact,
InitialMigration: InitialMigration as ContractArtifact,
LibBootstrap: LibBootstrap as ContractArtifact,
LibMigrate: LibMigrate as ContractArtifact,
LibMigrateStorage: LibMigrateStorage as ContractArtifact,
LibOwnableStorage: LibOwnableStorage as ContractArtifact,
LibProxyStorage: LibProxyStorage as ContractArtifact,
LibSimpleFunctionRegistryStorage: LibSimpleFunctionRegistryStorage as ContractArtifact,
ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact,
TestBasicMigration: TestBasicMigration as ContractArtifact,
TestInitialMigration: TestInitialMigration as ContractArtifact,
TestMigrator: TestMigrator as ContractArtifact,
TestSimpleFunctionRegistryFeatureImpl1: TestSimpleFunctionRegistryFeatureImpl1 as ContractArtifact,
TestSimpleFunctionRegistryFeatureImpl2: TestSimpleFunctionRegistryFeatureImpl2 as ContractArtifact,
TestZeroExFeature: TestZeroExFeature as ContractArtifact,

View File

@ -1,62 +0,0 @@
import { blockchainTests, expect, randomAddress } from '@0x/contracts-test-utils';
import { hexUtils, ZeroExRevertErrors } from '@0x/utils';
import { artifacts } from './artifacts';
import {
IOwnableContract,
ISimpleFunctionRegistryContract,
TestBasicMigrationContract,
ZeroExContract,
} from './wrappers';
blockchainTests.resets('Basic migration', env => {
let owner: string;
let zeroEx: ZeroExContract;
let migrator: TestBasicMigrationContract;
before(async () => {
[owner] = await env.getAccountAddressesAsync();
migrator = await TestBasicMigrationContract.deployFrom0xArtifactAsync(
artifacts.TestBasicMigration,
env.provider,
env.txDefaults,
artifacts,
);
const migrateCall = migrator.migrate(owner);
zeroEx = new ZeroExContract(await migrateCall.callAsync(), env.provider, env.txDefaults);
await migrateCall.awaitTransactionSuccessAsync();
});
describe('bootstrapping', () => {
it('Migrator cannot call bootstrap() again', async () => {
const tx = migrator.callBootstrap(zeroEx.address).awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(new ZeroExRevertErrors.Proxy.AlreadyBootstrappedError());
});
});
describe('Ownable feature', () => {
let ownable: IOwnableContract;
before(async () => {
ownable = new IOwnableContract(zeroEx.address, env.provider, env.txDefaults);
});
it('has the correct owner', async () => {
const actualOwner = await ownable.getOwner().callAsync();
expect(actualOwner).to.eq(owner);
});
});
describe('Registry feature', () => {
let registry: ISimpleFunctionRegistryContract;
before(async () => {
registry = new ISimpleFunctionRegistryContract(zeroEx.address, env.provider, env.txDefaults);
});
it('`extendSelf()` is unregistered', async () => {
const tx = registry.extendSelf(hexUtils.random(4), randomAddress()).callAsync();
return expect(tx).to.revertWith(new ZeroExRevertErrors.Proxy.NotImplementedError());
});
});
});

View File

@ -0,0 +1,94 @@
import { blockchainTests, expect, LogDecoder, randomAddress, verifyEventsFromLogs } from '@0x/contracts-test-utils';
import { hexUtils, OwnableRevertErrors, StringRevertError, ZeroExRevertErrors } from '@0x/utils';
import { artifacts } from '../artifacts';
import { initialMigrateAsync } from '../utils/migration';
import { IMigrateContract, IOwnableContract, TestMigratorContract, TestMigratorEvents } from '../wrappers';
blockchainTests.resets('Migrate feature', env => {
let owner: string;
let ownable: IOwnableContract;
let migrate: IMigrateContract;
let testMigrator: TestMigratorContract;
let succeedingMigrateFnCallData: string;
let failingMigrateFnCallData: string;
let revertingMigrateFnCallData: string;
let logDecoder: LogDecoder;
before(async () => {
logDecoder = new LogDecoder(env.web3Wrapper, artifacts);
[owner] = await env.getAccountAddressesAsync();
const zeroEx = await initialMigrateAsync(owner, env.provider, env.txDefaults);
ownable = new IOwnableContract(zeroEx.address, env.provider, env.txDefaults);
migrate = new IMigrateContract(zeroEx.address, env.provider, env.txDefaults);
testMigrator = await TestMigratorContract.deployFrom0xArtifactAsync(
artifacts.TestMigrator,
env.provider,
env.txDefaults,
artifacts,
);
succeedingMigrateFnCallData = testMigrator.succeedingMigrate().getABIEncodedTransactionData();
failingMigrateFnCallData = testMigrator.failingMigrate().getABIEncodedTransactionData();
revertingMigrateFnCallData = testMigrator.revertingMigrate().getABIEncodedTransactionData();
});
describe('migrate()', () => {
it('non-owner cannot call migrate()', async () => {
const notOwner = randomAddress();
const tx = migrate
.migrate(testMigrator.address, succeedingMigrateFnCallData)
.awaitTransactionSuccessAsync({ from: notOwner });
return expect(tx).to.revertWith(new OwnableRevertErrors.OnlyOwnerError(notOwner, owner));
});
it('can successfully execute a migration', async () => {
const receipt = await migrate
.migrate(testMigrator.address, succeedingMigrateFnCallData)
.awaitTransactionSuccessAsync({ from: owner });
const { logs } = logDecoder.decodeReceiptLogs(receipt);
verifyEventsFromLogs(
logs,
[
{
callData: succeedingMigrateFnCallData,
owner: migrate.address,
actualOwner: owner,
},
],
TestMigratorEvents.TestMigrateCalled,
);
});
it('owner is restored after a migration', async () => {
await migrate
.migrate(testMigrator.address, succeedingMigrateFnCallData)
.awaitTransactionSuccessAsync({ from: owner });
const currentOwner = await ownable.getOwner().callAsync();
expect(currentOwner).to.eq(owner);
});
it('failing migration reverts', async () => {
const tx = migrate
.migrate(testMigrator.address, failingMigrateFnCallData)
.awaitTransactionSuccessAsync({ from: owner });
return expect(tx).to.revertWith(
new ZeroExRevertErrors.Migrate.MigrateCallFailedError(
testMigrator.address,
hexUtils.rightPad('0xdeadbeef'),
),
);
});
it('reverting migration reverts', async () => {
const tx = migrate
.migrate(testMigrator.address, revertingMigrateFnCallData)
.awaitTransactionSuccessAsync({ from: owner });
return expect(tx).to.revertWith(
new ZeroExRevertErrors.Migrate.MigrateCallFailedError(
testMigrator.address,
new StringRevertError('OOPSIE').encode(),
),
);
});
});
});

View File

@ -1,30 +1,30 @@
import { blockchainTests, expect, randomAddress, verifyEventsFromLogs } from '@0x/contracts-test-utils';
import { OwnableRevertErrors } from '@0x/utils';
import { basicMigrateAsync } from '../utils/migration';
import { initialMigrateAsync } from '../utils/migration';
import { IOwnableContract, IOwnableEvents } from '../wrappers';
blockchainTests.resets('Ownable feature', env => {
const notOwner = randomAddress();
let owner: string;
let auth: IOwnableContract;
let ownable: IOwnableContract;
before(async () => {
[owner] = await env.getAccountAddressesAsync();
const zeroEx = await basicMigrateAsync(owner, env.provider, env.txDefaults);
auth = new IOwnableContract(zeroEx.address, env.provider, env.txDefaults);
const zeroEx = await initialMigrateAsync(owner, env.provider, env.txDefaults);
ownable = new IOwnableContract(zeroEx.address, env.provider, env.txDefaults);
});
describe('transferOwnership()', () => {
it('non-owner cannot transfer ownership', async () => {
const newOwner = randomAddress();
const tx = auth.transferOwnership(newOwner).callAsync({ from: notOwner });
const tx = ownable.transferOwnership(newOwner).callAsync({ from: notOwner });
return expect(tx).to.revertWith(new OwnableRevertErrors.OnlyOwnerError(notOwner, owner));
});
it('owner can transfer ownership', async () => {
const newOwner = randomAddress();
const receipt = await auth.transferOwnership(newOwner).awaitTransactionSuccessAsync({ from: owner });
const receipt = await ownable.transferOwnership(newOwner).awaitTransactionSuccessAsync({ from: owner });
verifyEventsFromLogs(
receipt.logs,
[
@ -35,7 +35,7 @@ blockchainTests.resets('Ownable feature', env => {
],
IOwnableEvents.OwnershipTransferred,
);
expect(await auth.getOwner().callAsync()).to.eq(newOwner);
expect(await ownable.getOwner().callAsync()).to.eq(newOwner);
});
});
});

View File

@ -2,7 +2,7 @@ import { blockchainTests, constants, expect, randomAddress, verifyEventsFromLogs
import { BigNumber, hexUtils, OwnableRevertErrors, ZeroExRevertErrors } from '@0x/utils';
import { artifacts } from '../artifacts';
import { basicMigrateAsync } from '../utils/migration';
import { initialMigrateAsync } from '../utils/migration';
import {
ISimpleFunctionRegistryContract,
ISimpleFunctionRegistryEvents,
@ -25,7 +25,7 @@ blockchainTests.resets('SimpleFunctionRegistry feature', env => {
before(async () => {
[owner] = await env.getAccountAddressesAsync();
zeroEx = await basicMigrateAsync(owner, env.provider, env.txDefaults);
zeroEx = await initialMigrateAsync(owner, env.provider, env.txDefaults);
registry = new ISimpleFunctionRegistryContract(zeroEx.address, env.provider, {
...env.txDefaults,
from: owner,

View File

@ -0,0 +1,57 @@
import { blockchainTests, expect } from '@0x/contracts-test-utils';
import { ZeroExRevertErrors } from '@0x/utils';
import { artifacts } from './artifacts';
import { IBootstrapContract, IOwnableContract, TestInitialMigrationContract, ZeroExContract } from './wrappers';
blockchainTests.resets('Initial migration', env => {
let owner: string;
let zeroEx: ZeroExContract;
let migrator: TestInitialMigrationContract;
let bootstrapFeature: IBootstrapContract;
before(async () => {
[owner] = await env.getAccountAddressesAsync();
migrator = await TestInitialMigrationContract.deployFrom0xArtifactAsync(
artifacts.TestInitialMigration,
env.provider,
env.txDefaults,
artifacts,
);
bootstrapFeature = new IBootstrapContract(
await migrator.bootstrapFeature().callAsync(),
env.provider,
env.txDefaults,
{},
);
const deployCall = migrator.deploy(owner);
zeroEx = new ZeroExContract(await deployCall.callAsync(), env.provider, env.txDefaults);
await deployCall.awaitTransactionSuccessAsync();
});
describe('bootstrapping', () => {
it('Migrator cannot call bootstrap() again', async () => {
const tx = migrator.callBootstrap(zeroEx.address).awaitTransactionSuccessAsync();
const selector = bootstrapFeature.getSelector('bootstrap');
return expect(tx).to.revertWith(new ZeroExRevertErrors.Proxy.NotImplementedError(selector));
});
});
describe('Ownable feature', () => {
let ownable: IOwnableContract;
before(async () => {
ownable = new IOwnableContract(zeroEx.address, env.provider, env.txDefaults);
});
it('has the correct owner', async () => {
const actualOwner = await ownable.getOwner().callAsync();
expect(actualOwner).to.eq(owner);
});
});
it('bootstrap feature self destructs after deployment', async () => {
const codeSize = await migrator.getCodeSizeOf(bootstrapFeature.address).callAsync();
expect(codeSize).to.bignumber.eq(0);
});
});

View File

@ -2,22 +2,22 @@ import { SupportedProvider } from '@0x/subproviders';
import { TxData } from 'ethereum-types';
import { artifacts } from '../artifacts';
import { BasicMigrationContract, ZeroExContract } from '../wrappers';
import { InitialMigrationContract, ZeroExContract } from '../wrappers';
// tslint:disable: completed-docs
export async function basicMigrateAsync(
export async function initialMigrateAsync(
owner: string,
provider: SupportedProvider,
txDefaults: Partial<TxData>,
): Promise<ZeroExContract> {
const migrator = await BasicMigrationContract.deployFrom0xArtifactAsync(
artifacts.BasicMigration,
const migrator = await InitialMigrationContract.deployFrom0xArtifactAsync(
artifacts.InitialMigration,
provider,
txDefaults,
artifacts,
);
const migrateCall = migrator.migrate(owner);
const zeroEx = new ZeroExContract(await migrateCall.callAsync(), provider, {});
await migrateCall.awaitTransactionSuccessAsync();
const deployCall = migrator.deploy(owner);
const zeroEx = new ZeroExContract(await deployCall.callAsync(), provider, {});
await deployCall.awaitTransactionSuccessAsync();
return zeroEx;
}

View File

@ -3,24 +3,32 @@
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
* -----------------------------------------------------------------------------
*/
export * from '../test/generated-wrappers/basic_migration';
export * from '../test/generated-wrappers/bootstrap';
export * from '../test/generated-wrappers/fixin_common';
export * from '../test/generated-wrappers/fixin_ownable';
export * from '../test/generated-wrappers/i_bootstrap';
export * from '../test/generated-wrappers/i_feature';
export * from '../test/generated-wrappers/i_migrate';
export * from '../test/generated-wrappers/i_ownable';
export * from '../test/generated-wrappers/i_simple_function_registry';
export * from '../test/generated-wrappers/i_test_simple_function_registry_feature';
export * from '../test/generated-wrappers/i_zero_ex_bootstrapper';
export * from '../test/generated-wrappers/initial_migration';
export * from '../test/generated-wrappers/lib_bootstrap';
export * from '../test/generated-wrappers/lib_common_rich_errors';
export * from '../test/generated-wrappers/lib_migrate';
export * from '../test/generated-wrappers/lib_migrate_rich_errors';
export * from '../test/generated-wrappers/lib_migrate_storage';
export * from '../test/generated-wrappers/lib_ownable_rich_errors';
export * from '../test/generated-wrappers/lib_ownable_storage';
export * from '../test/generated-wrappers/lib_proxy_rich_errors';
export * from '../test/generated-wrappers/lib_proxy_storage';
export * from '../test/generated-wrappers/lib_simple_function_registry_rich_errors';
export * from '../test/generated-wrappers/lib_simple_function_registry_storage';
export * from '../test/generated-wrappers/migrate';
export * from '../test/generated-wrappers/ownable';
export * from '../test/generated-wrappers/simple_function_registry';
export * from '../test/generated-wrappers/test_basic_migration';
export * from '../test/generated-wrappers/test_initial_migration';
export * from '../test/generated-wrappers/test_migrator';
export * from '../test/generated-wrappers/test_simple_function_registry_feature_impl1';
export * from '../test/generated-wrappers/test_simple_function_registry_feature_impl2';
export * from '../test/generated-wrappers/test_zero_ex_feature';

View File

@ -2,7 +2,7 @@ import { blockchainTests, constants, expect, verifyEventsFromLogs } from '@0x/co
import { BigNumber, ZeroExRevertErrors } from '@0x/utils';
import { artifacts } from './artifacts';
import { basicMigrateAsync } from './utils/migration';
import { initialMigrateAsync } from './utils/migration';
import {
IFeatureContract,
IOwnableContract,
@ -21,7 +21,7 @@ blockchainTests.resets('ZeroEx contract', env => {
before(async () => {
[owner] = await env.getAccountAddressesAsync();
zeroEx = await basicMigrateAsync(owner, env.provider, env.txDefaults);
zeroEx = await initialMigrateAsync(owner, env.provider, env.txDefaults);
ownable = new IOwnableContract(zeroEx.address, env.provider, env.txDefaults);
registry = new ISimpleFunctionRegistryContract(zeroEx.address, env.provider, env.txDefaults);
testFeature = new TestZeroExFeatureContract(zeroEx.address, env.provider, env.txDefaults);

View File

@ -3,27 +3,36 @@
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
"files": [
"generated-artifacts/IMigrate.json",
"generated-artifacts/IOwnable.json",
"generated-artifacts/ISimpleFunctionRegistry.json",
"generated-artifacts/ZeroEx.json",
"test/generated-artifacts/BasicMigration.json",
"test/generated-artifacts/Bootstrap.json",
"test/generated-artifacts/FixinCommon.json",
"test/generated-artifacts/FixinOwnable.json",
"test/generated-artifacts/IBootstrap.json",
"test/generated-artifacts/IFeature.json",
"test/generated-artifacts/IMigrate.json",
"test/generated-artifacts/IOwnable.json",
"test/generated-artifacts/ISimpleFunctionRegistry.json",
"test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json",
"test/generated-artifacts/IZeroExBootstrapper.json",
"test/generated-artifacts/InitialMigration.json",
"test/generated-artifacts/LibBootstrap.json",
"test/generated-artifacts/LibCommonRichErrors.json",
"test/generated-artifacts/LibMigrate.json",
"test/generated-artifacts/LibMigrateRichErrors.json",
"test/generated-artifacts/LibMigrateStorage.json",
"test/generated-artifacts/LibOwnableRichErrors.json",
"test/generated-artifacts/LibOwnableStorage.json",
"test/generated-artifacts/LibProxyRichErrors.json",
"test/generated-artifacts/LibProxyStorage.json",
"test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json",
"test/generated-artifacts/LibSimpleFunctionRegistryStorage.json",
"test/generated-artifacts/Migrate.json",
"test/generated-artifacts/Ownable.json",
"test/generated-artifacts/SimpleFunctionRegistry.json",
"test/generated-artifacts/TestBasicMigration.json",
"test/generated-artifacts/TestInitialMigration.json",
"test/generated-artifacts/TestMigrator.json",
"test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl1.json",
"test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl2.json",
"test/generated-artifacts/TestZeroExFeature.json",