@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:
@@ -20,7 +20,8 @@ pragma solidity ^0.6.5;
|
|||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
|
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 "./storage/LibProxyStorage.sol";
|
||||||
import "./errors/LibProxyRichErrors.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
|
/// @dev An extensible proxy contract that serves as a universal entry point for
|
||||||
/// interacting with the 0x protocol.
|
/// interacting with the 0x protocol.
|
||||||
contract ZeroEx {
|
contract ZeroEx {
|
||||||
|
|
||||||
// solhint-disable separate-by-one-line-in-contract,indent,var-name-mixedcase
|
// solhint-disable separate-by-one-line-in-contract,indent,var-name-mixedcase
|
||||||
|
|
||||||
using LibBytesV06 for bytes;
|
using LibBytesV06 for bytes;
|
||||||
|
|
||||||
/// @dev Construct this contract.
|
/// @dev Magic bytes returned by the bootstrapper to indicate success.
|
||||||
/// After constructing this contract, the deployer should call
|
bytes4 internal constant BOOTSTRAP_SUCCESS = 0xd150751b;
|
||||||
/// `bootstrap()` to seed the initial feature set.
|
|
||||||
constructor() public {
|
/// @dev Construct this contract and set the bootstrap migration contract.
|
||||||
// Set the `bootstrap()` caller to the deployer.
|
/// After constructing this contract, `bootstrap()` should be called
|
||||||
LibProxyStorage.getStorage().bootstrapCaller = msg.sender;
|
/// 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
|
// solhint-disable state-visibility
|
||||||
@@ -63,40 +70,6 @@ contract ZeroEx {
|
|||||||
|
|
||||||
// solhint-enable state-visibility
|
// 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.
|
/// @dev Get the implementation contract of a registered function.
|
||||||
/// @param selector The function selector.
|
/// @param selector The function selector.
|
||||||
/// @return impl The implementation contract address.
|
/// @return impl The implementation contract address.
|
||||||
|
@@ -17,17 +17,31 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
pragma solidity ^0.6.5;
|
pragma solidity ^0.6.5;
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "../src/migrations/BasicMigration.sol";
|
|
||||||
import "../src/interfaces/IZeroExBootstrapper.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract TestBasicMigration is
|
library LibMigrateRichErrors {
|
||||||
BasicMigration
|
|
||||||
|
// solhint-disable func-name-mixedcase
|
||||||
|
|
||||||
|
function AlreadyMigratingError()
|
||||||
|
internal
|
||||||
|
pure
|
||||||
|
returns (bytes memory)
|
||||||
{
|
{
|
||||||
function callBootstrap(ZeroEx zeroEx) external {
|
return abi.encodeWithSelector(
|
||||||
IZeroExBootstrapper[] memory bootstrappers;
|
bytes4(keccak256("AlreadyMigratingError()"))
|
||||||
zeroEx.bootstrap(bootstrappers);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MigrateCallFailedError(address target, bytes memory resultData)
|
||||||
|
internal
|
||||||
|
pure
|
||||||
|
returns (bytes memory)
|
||||||
|
{
|
||||||
|
return abi.encodeWithSelector(
|
||||||
|
bytes4(keccak256("MigrateCallFailedError(address,bytes)")),
|
||||||
|
target,
|
||||||
|
resultData
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -34,25 +34,39 @@ library LibProxyRichErrors {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlreadyBootstrappedError()
|
function InvalidBootstrapCallerError(address actual, address expected)
|
||||||
internal
|
|
||||||
pure
|
|
||||||
returns (bytes memory)
|
|
||||||
{
|
|
||||||
return abi.encodeWithSelector(
|
|
||||||
bytes4(keccak256("AlreadyBootstrappedError()"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function InvalidBootstrapCallerError(address caller, address expectedCaller)
|
|
||||||
internal
|
internal
|
||||||
pure
|
pure
|
||||||
returns (bytes memory)
|
returns (bytes memory)
|
||||||
{
|
{
|
||||||
return abi.encodeWithSelector(
|
return abi.encodeWithSelector(
|
||||||
bytes4(keccak256("InvalidBootstrapCallerError(address,address)")),
|
bytes4(keccak256("InvalidBootstrapCallerError(address,address)")),
|
||||||
caller,
|
actual,
|
||||||
expectedCaller
|
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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
87
contracts/zero-ex/contracts/src/features/Bootstrap.sol
Normal file
87
contracts/zero-ex/contracts/src/features/Bootstrap.sol
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@@ -20,13 +20,12 @@ pragma solidity ^0.6.5;
|
|||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
|
||||||
/// @dev Interface for a bootstrapping contract that the `ZeroEx` proxy.
|
/// @dev Detachable `bootstrap()` feature.
|
||||||
interface IZeroExBootstrapper {
|
interface IBootstrap {
|
||||||
|
|
||||||
/// @dev Sets up the initial state of the `ZeroEx` contract.
|
/// @dev Bootstrap the initial feature set of this contract by delegatecalling
|
||||||
/// The `ZeroEx` contract will delegatecall this function so the
|
/// into `_bootstrapper`. Before exiting the `bootstrap()` function will
|
||||||
/// bootstrapper should use this function to register initial
|
/// deregister itself from the proxy to prevent being called again.
|
||||||
/// features.
|
/// @param callData The call data to execute on `_bootstrapper`.
|
||||||
/// @param impl The implementation contract.
|
function bootstrap(bytes calldata callData) external;
|
||||||
function bootstrap(address impl) external;
|
|
||||||
}
|
}
|
43
contracts/zero-ex/contracts/src/features/IMigrate.sol
Normal file
43
contracts/zero-ex/contracts/src/features/IMigrate.sol
Normal 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);
|
||||||
|
}
|
@@ -19,8 +19,6 @@
|
|||||||
pragma solidity ^0.6.5;
|
pragma solidity ^0.6.5;
|
||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "../interfaces/IZeroExBootstrapper.sol";
|
|
||||||
|
|
||||||
|
|
||||||
/// @dev Basic registry management features.
|
/// @dev Basic registry management features.
|
||||||
interface ISimpleFunctionRegistry {
|
interface ISimpleFunctionRegistry {
|
||||||
@@ -41,12 +39,6 @@ interface ISimpleFunctionRegistry {
|
|||||||
/// @param impl The implementation contract for the function.
|
/// @param impl The implementation contract for the function.
|
||||||
function extend(bytes4 selector, address impl) external;
|
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.
|
/// @dev Retrieve the length of the rollback history for a function.
|
||||||
/// @param selector The function selector.
|
/// @param selector The function selector.
|
||||||
/// @return rollbackLength The number of items in the rollback history for
|
/// @return rollbackLength The number of items in the rollback history for
|
94
contracts/zero-ex/contracts/src/features/Migrate.sol
Normal file
94
contracts/zero-ex/contracts/src/features/Migrate.sol
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -20,19 +20,18 @@ pragma solidity ^0.6.5;
|
|||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "../fixins/FixinOwnable.sol";
|
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 "../errors/LibOwnableRichErrors.sol";
|
||||||
import "../storage/LibOwnableStorage.sol";
|
import "../storage/LibOwnableStorage.sol";
|
||||||
|
import "../migrations/LibBootstrap.sol";
|
||||||
|
import "./IFeature.sol";
|
||||||
|
import "./IOwnable.sol";
|
||||||
|
import "./ISimpleFunctionRegistry.sol";
|
||||||
|
|
||||||
|
|
||||||
/// @dev Owner management features.
|
/// @dev Owner management features.
|
||||||
contract Ownable is
|
contract Ownable is
|
||||||
IFeature,
|
IFeature,
|
||||||
IOwnable,
|
IOwnable,
|
||||||
IZeroExBootstrapper,
|
|
||||||
FixinOwnable
|
FixinOwnable
|
||||||
{
|
{
|
||||||
// solhint-disable const-name-snakecase
|
// solhint-disable const-name-snakecase
|
||||||
@@ -42,15 +41,20 @@ contract Ownable is
|
|||||||
/// @dev Version of this feature.
|
/// @dev Version of this feature.
|
||||||
uint256 constant public override FEATURE_VERSION = (1 << 64) | (0 << 32) | (0);
|
uint256 constant public override FEATURE_VERSION = (1 << 64) | (0 << 32) | (0);
|
||||||
|
|
||||||
/// @dev Initializes the authority feature.
|
/// @dev Initializes this feature. The intial owner will be set to this (ZeroEx)
|
||||||
/// @param impl The actual address of this feature contract.
|
/// to allow the bootstrappers to call `extend()`. Ownership should be
|
||||||
function bootstrap(address impl) external override {
|
/// 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.
|
// Set the owner.
|
||||||
LibOwnableStorage.getStorage().owner = msg.sender;
|
LibOwnableStorage.getStorage().owner = address(this);
|
||||||
|
|
||||||
// Register feature functions.
|
// Register feature functions.
|
||||||
ISimpleFunctionRegistry(address(this)).extendSelf(this.transferOwnership.selector, impl);
|
ISimpleFunctionRegistry(address(this)).extend(this.transferOwnership.selector, impl);
|
||||||
ISimpleFunctionRegistry(address(this)).extendSelf(this.getOwner.selector, impl);
|
ISimpleFunctionRegistry(address(this)).extend(this.getOwner.selector, impl);
|
||||||
|
return LibBootstrap.BOOTSTRAP_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Change the owner of this contract.
|
/// @dev Change the owner of this contract.
|
||||||
|
@@ -19,20 +19,19 @@
|
|||||||
pragma solidity ^0.6.5;
|
pragma solidity ^0.6.5;
|
||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "../interfaces/IFeature.sol";
|
|
||||||
import "../interfaces/ISimpleFunctionRegistry.sol";
|
|
||||||
import "../interfaces/IZeroExBootstrapper.sol";
|
|
||||||
import "../fixins/FixinOwnable.sol";
|
import "../fixins/FixinOwnable.sol";
|
||||||
import "../storage/LibProxyStorage.sol";
|
import "../storage/LibProxyStorage.sol";
|
||||||
import "../storage/LibSimpleFunctionRegistryStorage.sol";
|
import "../storage/LibSimpleFunctionRegistryStorage.sol";
|
||||||
import "../errors/LibSimpleFunctionRegistryRichErrors.sol";
|
import "../errors/LibSimpleFunctionRegistryRichErrors.sol";
|
||||||
|
import "../migrations/LibBootstrap.sol";
|
||||||
|
import "./IFeature.sol";
|
||||||
|
import "./ISimpleFunctionRegistry.sol";
|
||||||
|
|
||||||
|
|
||||||
/// @dev Basic registry management features.
|
/// @dev Basic registry management features.
|
||||||
contract SimpleFunctionRegistry is
|
contract SimpleFunctionRegistry is
|
||||||
IFeature,
|
IFeature,
|
||||||
ISimpleFunctionRegistry,
|
ISimpleFunctionRegistry,
|
||||||
IZeroExBootstrapper,
|
|
||||||
FixinOwnable
|
FixinOwnable
|
||||||
{
|
{
|
||||||
// solhint-disable const-name-snakecase
|
// solhint-disable const-name-snakecase
|
||||||
@@ -42,17 +41,18 @@ contract SimpleFunctionRegistry is
|
|||||||
/// @dev Version of this feature.
|
/// @dev Version of this feature.
|
||||||
uint256 constant public override FEATURE_VERSION = (1 << 64) | (0 << 32) | (0);
|
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.
|
/// @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).
|
// Register the registration functions (inception vibes).
|
||||||
_extend(this.extend.selector, impl);
|
_extend(this.extend.selector, impl);
|
||||||
_extend(this.extendSelf.selector, impl);
|
|
||||||
// Register the rollback function.
|
// Register the rollback function.
|
||||||
_extend(this.rollback.selector, impl);
|
_extend(this.rollback.selector, impl);
|
||||||
// Register getters.
|
// Register getters.
|
||||||
_extend(this.getRollbackLength.selector, impl);
|
_extend(this.getRollbackLength.selector, impl);
|
||||||
_extend(this.getRollbackEntryAtIndex.selector, impl);
|
_extend(this.getRollbackEntryAtIndex.selector, impl);
|
||||||
|
return LibBootstrap.BOOTSTRAP_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Roll back to a prior implementation of a function.
|
/// @dev Roll back to a prior implementation of a function.
|
||||||
@@ -108,18 +108,6 @@ contract SimpleFunctionRegistry is
|
|||||||
_extend(selector, impl);
|
_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.
|
/// @dev Retrieve the length of the rollback history for a function.
|
||||||
/// @param selector The function selector.
|
/// @param selector The function selector.
|
||||||
/// @return rollbackLength The number of items in the rollback history for
|
/// @return rollbackLength The number of items in the rollback history for
|
||||||
|
@@ -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.
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
50
contracts/zero-ex/contracts/src/migrations/LibBootstrap.sol
Normal file
50
contracts/zero-ex/contracts/src/migrations/LibBootstrap.sol
Normal 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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
contracts/zero-ex/contracts/src/migrations/LibMigrate.sol
Normal file
50
contracts/zero-ex/contracts/src/migrations/LibMigrate.sol
Normal 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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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 }
|
||||||
|
}
|
||||||
|
}
|
@@ -29,8 +29,6 @@ library LibProxyStorage {
|
|||||||
|
|
||||||
/// @dev Storage bucket for proxy contract.
|
/// @dev Storage bucket for proxy contract.
|
||||||
struct Storage {
|
struct Storage {
|
||||||
// The allowed caller for `bootstrap()`.
|
|
||||||
address bootstrapCaller;
|
|
||||||
// Mapping of function selector -> function implementation
|
// Mapping of function selector -> function implementation
|
||||||
mapping(bytes4 => address) impls;
|
mapping(bytes4 => address) impls;
|
||||||
}
|
}
|
||||||
|
46
contracts/zero-ex/contracts/test/TestInitialMigration.sol
Normal file
46
contracts/zero-ex/contracts/test/TestInitialMigration.sol
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
55
contracts/zero-ex/contracts/test/TestMigrator.sol
Normal file
55
contracts/zero-ex/contracts/test/TestMigrator.sol
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
@@ -38,9 +38,9 @@
|
|||||||
"docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
|
"docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
|
||||||
},
|
},
|
||||||
"config": {
|
"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: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": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@@ -5,11 +5,13 @@
|
|||||||
*/
|
*/
|
||||||
import { ContractArtifact } from 'ethereum-types';
|
import { ContractArtifact } from 'ethereum-types';
|
||||||
|
|
||||||
|
import * as IMigrate from '../generated-artifacts/IMigrate.json';
|
||||||
import * as IOwnable from '../generated-artifacts/IOwnable.json';
|
import * as IOwnable from '../generated-artifacts/IOwnable.json';
|
||||||
import * as ISimpleFunctionRegistry from '../generated-artifacts/ISimpleFunctionRegistry.json';
|
import * as ISimpleFunctionRegistry from '../generated-artifacts/ISimpleFunctionRegistry.json';
|
||||||
import * as ZeroEx from '../generated-artifacts/ZeroEx.json';
|
import * as ZeroEx from '../generated-artifacts/ZeroEx.json';
|
||||||
export const artifacts = {
|
export const artifacts = {
|
||||||
ZeroEx: ZeroEx as ContractArtifact,
|
ZeroEx: ZeroEx as ContractArtifact,
|
||||||
|
IMigrate: IMigrate as ContractArtifact,
|
||||||
IOwnable: IOwnable as ContractArtifact,
|
IOwnable: IOwnable as ContractArtifact,
|
||||||
ISimpleFunctionRegistry: ISimpleFunctionRegistry as ContractArtifact,
|
ISimpleFunctionRegistry: ISimpleFunctionRegistry as ContractArtifact,
|
||||||
};
|
};
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
* 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_ownable';
|
||||||
export * from '../generated-wrappers/i_simple_function_registry';
|
export * from '../generated-wrappers/i_simple_function_registry';
|
||||||
export * from '../generated-wrappers/zero_ex';
|
export * from '../generated-wrappers/zero_ex';
|
||||||
|
@@ -5,24 +5,32 @@
|
|||||||
*/
|
*/
|
||||||
import { ContractArtifact } from 'ethereum-types';
|
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 FixinCommon from '../test/generated-artifacts/FixinCommon.json';
|
||||||
import * as FixinOwnable from '../test/generated-artifacts/FixinOwnable.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 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 IOwnable from '../test/generated-artifacts/IOwnable.json';
|
||||||
import * as ISimpleFunctionRegistry from '../test/generated-artifacts/ISimpleFunctionRegistry.json';
|
import * as ISimpleFunctionRegistry from '../test/generated-artifacts/ISimpleFunctionRegistry.json';
|
||||||
import * as ITestSimpleFunctionRegistryFeature from '../test/generated-artifacts/ITestSimpleFunctionRegistryFeature.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 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 LibOwnableRichErrors from '../test/generated-artifacts/LibOwnableRichErrors.json';
|
||||||
import * as LibOwnableStorage from '../test/generated-artifacts/LibOwnableStorage.json';
|
import * as LibOwnableStorage from '../test/generated-artifacts/LibOwnableStorage.json';
|
||||||
import * as LibProxyRichErrors from '../test/generated-artifacts/LibProxyRichErrors.json';
|
import * as LibProxyRichErrors from '../test/generated-artifacts/LibProxyRichErrors.json';
|
||||||
import * as LibProxyStorage from '../test/generated-artifacts/LibProxyStorage.json';
|
import * as LibProxyStorage from '../test/generated-artifacts/LibProxyStorage.json';
|
||||||
import * as LibSimpleFunctionRegistryRichErrors from '../test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json';
|
import * as LibSimpleFunctionRegistryRichErrors from '../test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json';
|
||||||
import * as LibSimpleFunctionRegistryStorage from '../test/generated-artifacts/LibSimpleFunctionRegistryStorage.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 Ownable from '../test/generated-artifacts/Ownable.json';
|
||||||
import * as SimpleFunctionRegistry from '../test/generated-artifacts/SimpleFunctionRegistry.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 TestSimpleFunctionRegistryFeatureImpl1 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl1.json';
|
||||||
import * as TestSimpleFunctionRegistryFeatureImpl2 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl2.json';
|
import * as TestSimpleFunctionRegistryFeatureImpl2 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl2.json';
|
||||||
import * as TestZeroExFeature from '../test/generated-artifacts/TestZeroExFeature.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 = {
|
export const artifacts = {
|
||||||
ZeroEx: ZeroEx as ContractArtifact,
|
ZeroEx: ZeroEx as ContractArtifact,
|
||||||
LibCommonRichErrors: LibCommonRichErrors as ContractArtifact,
|
LibCommonRichErrors: LibCommonRichErrors as ContractArtifact,
|
||||||
|
LibMigrateRichErrors: LibMigrateRichErrors as ContractArtifact,
|
||||||
LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact,
|
LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact,
|
||||||
LibProxyRichErrors: LibProxyRichErrors as ContractArtifact,
|
LibProxyRichErrors: LibProxyRichErrors as ContractArtifact,
|
||||||
LibSimpleFunctionRegistryRichErrors: LibSimpleFunctionRegistryRichErrors 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,
|
Ownable: Ownable as ContractArtifact,
|
||||||
SimpleFunctionRegistry: SimpleFunctionRegistry as ContractArtifact,
|
SimpleFunctionRegistry: SimpleFunctionRegistry as ContractArtifact,
|
||||||
FixinCommon: FixinCommon as ContractArtifact,
|
FixinCommon: FixinCommon as ContractArtifact,
|
||||||
FixinOwnable: FixinOwnable as ContractArtifact,
|
FixinOwnable: FixinOwnable as ContractArtifact,
|
||||||
IFeature: IFeature as ContractArtifact,
|
InitialMigration: InitialMigration as ContractArtifact,
|
||||||
IOwnable: IOwnable as ContractArtifact,
|
LibBootstrap: LibBootstrap as ContractArtifact,
|
||||||
ISimpleFunctionRegistry: ISimpleFunctionRegistry as ContractArtifact,
|
LibMigrate: LibMigrate as ContractArtifact,
|
||||||
IZeroExBootstrapper: IZeroExBootstrapper as ContractArtifact,
|
LibMigrateStorage: LibMigrateStorage as ContractArtifact,
|
||||||
BasicMigration: BasicMigration as ContractArtifact,
|
|
||||||
LibOwnableStorage: LibOwnableStorage as ContractArtifact,
|
LibOwnableStorage: LibOwnableStorage as ContractArtifact,
|
||||||
LibProxyStorage: LibProxyStorage as ContractArtifact,
|
LibProxyStorage: LibProxyStorage as ContractArtifact,
|
||||||
LibSimpleFunctionRegistryStorage: LibSimpleFunctionRegistryStorage as ContractArtifact,
|
LibSimpleFunctionRegistryStorage: LibSimpleFunctionRegistryStorage as ContractArtifact,
|
||||||
ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact,
|
ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact,
|
||||||
TestBasicMigration: TestBasicMigration as ContractArtifact,
|
TestInitialMigration: TestInitialMigration as ContractArtifact,
|
||||||
|
TestMigrator: TestMigrator as ContractArtifact,
|
||||||
TestSimpleFunctionRegistryFeatureImpl1: TestSimpleFunctionRegistryFeatureImpl1 as ContractArtifact,
|
TestSimpleFunctionRegistryFeatureImpl1: TestSimpleFunctionRegistryFeatureImpl1 as ContractArtifact,
|
||||||
TestSimpleFunctionRegistryFeatureImpl2: TestSimpleFunctionRegistryFeatureImpl2 as ContractArtifact,
|
TestSimpleFunctionRegistryFeatureImpl2: TestSimpleFunctionRegistryFeatureImpl2 as ContractArtifact,
|
||||||
TestZeroExFeature: TestZeroExFeature as ContractArtifact,
|
TestZeroExFeature: TestZeroExFeature as ContractArtifact,
|
||||||
|
@@ -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());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
94
contracts/zero-ex/test/features/migrate_test.ts
Normal file
94
contracts/zero-ex/test/features/migrate_test.ts
Normal 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(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -1,30 +1,30 @@
|
|||||||
import { blockchainTests, expect, randomAddress, verifyEventsFromLogs } from '@0x/contracts-test-utils';
|
import { blockchainTests, expect, randomAddress, verifyEventsFromLogs } from '@0x/contracts-test-utils';
|
||||||
import { OwnableRevertErrors } from '@0x/utils';
|
import { OwnableRevertErrors } from '@0x/utils';
|
||||||
|
|
||||||
import { basicMigrateAsync } from '../utils/migration';
|
import { initialMigrateAsync } from '../utils/migration';
|
||||||
import { IOwnableContract, IOwnableEvents } from '../wrappers';
|
import { IOwnableContract, IOwnableEvents } from '../wrappers';
|
||||||
|
|
||||||
blockchainTests.resets('Ownable feature', env => {
|
blockchainTests.resets('Ownable feature', env => {
|
||||||
const notOwner = randomAddress();
|
const notOwner = randomAddress();
|
||||||
let owner: string;
|
let owner: string;
|
||||||
let auth: IOwnableContract;
|
let ownable: IOwnableContract;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
[owner] = await env.getAccountAddressesAsync();
|
[owner] = await env.getAccountAddressesAsync();
|
||||||
const zeroEx = await basicMigrateAsync(owner, env.provider, env.txDefaults);
|
const zeroEx = await initialMigrateAsync(owner, env.provider, env.txDefaults);
|
||||||
auth = new IOwnableContract(zeroEx.address, env.provider, env.txDefaults);
|
ownable = new IOwnableContract(zeroEx.address, env.provider, env.txDefaults);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('transferOwnership()', () => {
|
describe('transferOwnership()', () => {
|
||||||
it('non-owner cannot transfer ownership', async () => {
|
it('non-owner cannot transfer ownership', async () => {
|
||||||
const newOwner = randomAddress();
|
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));
|
return expect(tx).to.revertWith(new OwnableRevertErrors.OnlyOwnerError(notOwner, owner));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('owner can transfer ownership', async () => {
|
it('owner can transfer ownership', async () => {
|
||||||
const newOwner = randomAddress();
|
const newOwner = randomAddress();
|
||||||
const receipt = await auth.transferOwnership(newOwner).awaitTransactionSuccessAsync({ from: owner });
|
const receipt = await ownable.transferOwnership(newOwner).awaitTransactionSuccessAsync({ from: owner });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
[
|
[
|
||||||
@@ -35,7 +35,7 @@ blockchainTests.resets('Ownable feature', env => {
|
|||||||
],
|
],
|
||||||
IOwnableEvents.OwnershipTransferred,
|
IOwnableEvents.OwnershipTransferred,
|
||||||
);
|
);
|
||||||
expect(await auth.getOwner().callAsync()).to.eq(newOwner);
|
expect(await ownable.getOwner().callAsync()).to.eq(newOwner);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -2,7 +2,7 @@ import { blockchainTests, constants, expect, randomAddress, verifyEventsFromLogs
|
|||||||
import { BigNumber, hexUtils, OwnableRevertErrors, ZeroExRevertErrors } from '@0x/utils';
|
import { BigNumber, hexUtils, OwnableRevertErrors, ZeroExRevertErrors } from '@0x/utils';
|
||||||
|
|
||||||
import { artifacts } from '../artifacts';
|
import { artifacts } from '../artifacts';
|
||||||
import { basicMigrateAsync } from '../utils/migration';
|
import { initialMigrateAsync } from '../utils/migration';
|
||||||
import {
|
import {
|
||||||
ISimpleFunctionRegistryContract,
|
ISimpleFunctionRegistryContract,
|
||||||
ISimpleFunctionRegistryEvents,
|
ISimpleFunctionRegistryEvents,
|
||||||
@@ -25,7 +25,7 @@ blockchainTests.resets('SimpleFunctionRegistry feature', env => {
|
|||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
[owner] = await env.getAccountAddressesAsync();
|
[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, {
|
registry = new ISimpleFunctionRegistryContract(zeroEx.address, env.provider, {
|
||||||
...env.txDefaults,
|
...env.txDefaults,
|
||||||
from: owner,
|
from: owner,
|
||||||
|
57
contracts/zero-ex/test/initial_migration_test.ts
Normal file
57
contracts/zero-ex/test/initial_migration_test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
@@ -2,22 +2,22 @@ import { SupportedProvider } from '@0x/subproviders';
|
|||||||
import { TxData } from 'ethereum-types';
|
import { TxData } from 'ethereum-types';
|
||||||
|
|
||||||
import { artifacts } from '../artifacts';
|
import { artifacts } from '../artifacts';
|
||||||
import { BasicMigrationContract, ZeroExContract } from '../wrappers';
|
import { InitialMigrationContract, ZeroExContract } from '../wrappers';
|
||||||
|
|
||||||
// tslint:disable: completed-docs
|
// tslint:disable: completed-docs
|
||||||
export async function basicMigrateAsync(
|
export async function initialMigrateAsync(
|
||||||
owner: string,
|
owner: string,
|
||||||
provider: SupportedProvider,
|
provider: SupportedProvider,
|
||||||
txDefaults: Partial<TxData>,
|
txDefaults: Partial<TxData>,
|
||||||
): Promise<ZeroExContract> {
|
): Promise<ZeroExContract> {
|
||||||
const migrator = await BasicMigrationContract.deployFrom0xArtifactAsync(
|
const migrator = await InitialMigrationContract.deployFrom0xArtifactAsync(
|
||||||
artifacts.BasicMigration,
|
artifacts.InitialMigration,
|
||||||
provider,
|
provider,
|
||||||
txDefaults,
|
txDefaults,
|
||||||
artifacts,
|
artifacts,
|
||||||
);
|
);
|
||||||
const migrateCall = migrator.migrate(owner);
|
const deployCall = migrator.deploy(owner);
|
||||||
const zeroEx = new ZeroExContract(await migrateCall.callAsync(), provider, {});
|
const zeroEx = new ZeroExContract(await deployCall.callAsync(), provider, {});
|
||||||
await migrateCall.awaitTransactionSuccessAsync();
|
await deployCall.awaitTransactionSuccessAsync();
|
||||||
return zeroEx;
|
return zeroEx;
|
||||||
}
|
}
|
||||||
|
@@ -3,24 +3,32 @@
|
|||||||
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
* 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_common';
|
||||||
export * from '../test/generated-wrappers/fixin_ownable';
|
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_feature';
|
||||||
|
export * from '../test/generated-wrappers/i_migrate';
|
||||||
export * from '../test/generated-wrappers/i_ownable';
|
export * from '../test/generated-wrappers/i_ownable';
|
||||||
export * from '../test/generated-wrappers/i_simple_function_registry';
|
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_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_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_rich_errors';
|
||||||
export * from '../test/generated-wrappers/lib_ownable_storage';
|
export * from '../test/generated-wrappers/lib_ownable_storage';
|
||||||
export * from '../test/generated-wrappers/lib_proxy_rich_errors';
|
export * from '../test/generated-wrappers/lib_proxy_rich_errors';
|
||||||
export * from '../test/generated-wrappers/lib_proxy_storage';
|
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_rich_errors';
|
||||||
export * from '../test/generated-wrappers/lib_simple_function_registry_storage';
|
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/ownable';
|
||||||
export * from '../test/generated-wrappers/simple_function_registry';
|
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_impl1';
|
||||||
export * from '../test/generated-wrappers/test_simple_function_registry_feature_impl2';
|
export * from '../test/generated-wrappers/test_simple_function_registry_feature_impl2';
|
||||||
export * from '../test/generated-wrappers/test_zero_ex_feature';
|
export * from '../test/generated-wrappers/test_zero_ex_feature';
|
||||||
|
@@ -2,7 +2,7 @@ import { blockchainTests, constants, expect, verifyEventsFromLogs } from '@0x/co
|
|||||||
import { BigNumber, ZeroExRevertErrors } from '@0x/utils';
|
import { BigNumber, ZeroExRevertErrors } from '@0x/utils';
|
||||||
|
|
||||||
import { artifacts } from './artifacts';
|
import { artifacts } from './artifacts';
|
||||||
import { basicMigrateAsync } from './utils/migration';
|
import { initialMigrateAsync } from './utils/migration';
|
||||||
import {
|
import {
|
||||||
IFeatureContract,
|
IFeatureContract,
|
||||||
IOwnableContract,
|
IOwnableContract,
|
||||||
@@ -21,7 +21,7 @@ blockchainTests.resets('ZeroEx contract', env => {
|
|||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
[owner] = await env.getAccountAddressesAsync();
|
[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);
|
ownable = new IOwnableContract(zeroEx.address, env.provider, env.txDefaults);
|
||||||
registry = new ISimpleFunctionRegistryContract(zeroEx.address, env.provider, env.txDefaults);
|
registry = new ISimpleFunctionRegistryContract(zeroEx.address, env.provider, env.txDefaults);
|
||||||
testFeature = new TestZeroExFeatureContract(zeroEx.address, env.provider, env.txDefaults);
|
testFeature = new TestZeroExFeatureContract(zeroEx.address, env.provider, env.txDefaults);
|
||||||
|
@@ -3,27 +3,36 @@
|
|||||||
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
|
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
|
||||||
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
|
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
|
||||||
"files": [
|
"files": [
|
||||||
|
"generated-artifacts/IMigrate.json",
|
||||||
"generated-artifacts/IOwnable.json",
|
"generated-artifacts/IOwnable.json",
|
||||||
"generated-artifacts/ISimpleFunctionRegistry.json",
|
"generated-artifacts/ISimpleFunctionRegistry.json",
|
||||||
"generated-artifacts/ZeroEx.json",
|
"generated-artifacts/ZeroEx.json",
|
||||||
"test/generated-artifacts/BasicMigration.json",
|
"test/generated-artifacts/Bootstrap.json",
|
||||||
"test/generated-artifacts/FixinCommon.json",
|
"test/generated-artifacts/FixinCommon.json",
|
||||||
"test/generated-artifacts/FixinOwnable.json",
|
"test/generated-artifacts/FixinOwnable.json",
|
||||||
|
"test/generated-artifacts/IBootstrap.json",
|
||||||
"test/generated-artifacts/IFeature.json",
|
"test/generated-artifacts/IFeature.json",
|
||||||
|
"test/generated-artifacts/IMigrate.json",
|
||||||
"test/generated-artifacts/IOwnable.json",
|
"test/generated-artifacts/IOwnable.json",
|
||||||
"test/generated-artifacts/ISimpleFunctionRegistry.json",
|
"test/generated-artifacts/ISimpleFunctionRegistry.json",
|
||||||
"test/generated-artifacts/ITestSimpleFunctionRegistryFeature.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/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/LibOwnableRichErrors.json",
|
||||||
"test/generated-artifacts/LibOwnableStorage.json",
|
"test/generated-artifacts/LibOwnableStorage.json",
|
||||||
"test/generated-artifacts/LibProxyRichErrors.json",
|
"test/generated-artifacts/LibProxyRichErrors.json",
|
||||||
"test/generated-artifacts/LibProxyStorage.json",
|
"test/generated-artifacts/LibProxyStorage.json",
|
||||||
"test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json",
|
"test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json",
|
||||||
"test/generated-artifacts/LibSimpleFunctionRegistryStorage.json",
|
"test/generated-artifacts/LibSimpleFunctionRegistryStorage.json",
|
||||||
|
"test/generated-artifacts/Migrate.json",
|
||||||
"test/generated-artifacts/Ownable.json",
|
"test/generated-artifacts/Ownable.json",
|
||||||
"test/generated-artifacts/SimpleFunctionRegistry.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/TestSimpleFunctionRegistryFeatureImpl1.json",
|
||||||
"test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl2.json",
|
"test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl2.json",
|
||||||
"test/generated-artifacts/TestZeroExFeature.json",
|
"test/generated-artifacts/TestZeroExFeature.json",
|
||||||
|
Reference in New Issue
Block a user