@0x/contracts-zero-ex: Lock the caller of InitialMigration.deploy() and actually self-destruct.

This commit is contained in:
Lawrence Forman 2020-04-29 13:48:11 -04:00
parent 3c33a93b9c
commit 69f7343be5
4 changed files with 76 additions and 13 deletions

View File

@ -28,25 +28,42 @@ import "./LibBootstrap.sol";
/// @dev A contract for deploying and configuring a minimal ZeroEx contract. /// @dev A contract for deploying and configuring a minimal ZeroEx contract.
contract InitialMigration { contract InitialMigration {
/// @dev The deployed ZereEx instance.
ZeroEx private _zeroEx; /// @dev The allowed caller of `deploy()`. In production, this would be
/// the governor.
address public immutable deployer;
/// @dev The real address of this contract.
address private immutable _implementation;
/// @dev Instantiate this contract and set the allowed caller of `deploy()`
/// to `deployer_`.
/// @param deployer_ The allowed caller of `deploy()`.
constructor(address deployer_) public {
deployer = deployer_;
_implementation = address(this);
}
/// @dev Deploy the `ZeroEx` contract with the minimum feature set, /// @dev Deploy the `ZeroEx` contract with the minimum feature set,
/// transfers ownership to `owner`, then self-destructs. /// transfers ownership to `owner`, then self-destructs.
/// Only callable by `deployer` set in the contstructor.
/// @param owner The owner of the contract. /// @param owner The owner of the contract.
/// @return zeroEx The deployed and configured `ZeroEx` contract. /// @return zeroEx The deployed and configured `ZeroEx` contract.
function deploy(address owner) public virtual returns (ZeroEx zeroEx) { function deploy(address payable owner) public virtual returns (ZeroEx zeroEx) {
// Must not have already been run. // Must be called by the allowed deployer.
require(address(_zeroEx) == address(0), "InitialMigration/ALREADY_DEPLOYED"); require(msg.sender == deployer, "InitialMigration/INVALID_SENDER");
// Deploy the ZeroEx contract, setting ourselves as the bootstrapper. // Deploy the ZeroEx contract, setting ourselves as the bootstrapper.
zeroEx = _zeroEx = new ZeroEx(); zeroEx = new ZeroEx();
// Bootstrap the initial feature set. // Bootstrap the initial feature set.
IBootstrap(address(zeroEx)).bootstrap( IBootstrap(address(zeroEx)).bootstrap(
address(this), address(this),
abi.encodeWithSelector(this.bootstrap.selector, owner) abi.encodeWithSelector(this.bootstrap.selector, owner)
); );
// Self-destruct. This contract should not hold any funds but we send
// them to the owner just in case.
this.die(owner);
} }
/// @dev Sets up the initial state of the `ZeroEx` contract. /// @dev Sets up the initial state of the `ZeroEx` contract.
@ -76,4 +93,11 @@ contract InitialMigration {
success = LibBootstrap.BOOTSTRAP_SUCCESS; success = LibBootstrap.BOOTSTRAP_SUCCESS;
} }
/// @dev Self-destructs this contract. Only callable by this contract.
/// @param ethRecipient Who to transfer outstanding ETH to.
function die(address payable ethRecipient) public virtual {
require(msg.sender == _implementation, "InitialMigration/INVALID_SENDER");
selfdestruct(ethRecipient);
}
} }

View File

@ -28,6 +28,10 @@ contract TestInitialMigration is
InitialMigration InitialMigration
{ {
address public bootstrapFeature; address public bootstrapFeature;
address public dieRecipient;
// solhint-disable-next-line no-empty-blocks
constructor(address deployer) public InitialMigration(deployer) {}
function callBootstrap(ZeroEx zeroEx) external { function callBootstrap(ZeroEx zeroEx) external {
IBootstrap(address(zeroEx)).bootstrap(address(this), new bytes(0)); IBootstrap(address(zeroEx)).bootstrap(address(this), new bytes(0));
@ -43,4 +47,8 @@ contract TestInitialMigration is
bootstrapFeature = ZeroEx(address(uint160(address(this)))) bootstrapFeature = ZeroEx(address(uint160(address(this))))
.getFunctionImplementation(IBootstrap.bootstrap.selector); .getFunctionImplementation(IBootstrap.bootstrap.selector);
} }
function die(address payable ethRecipient) public override {
dieRecipient = ethRecipient;
}
} }

View File

@ -1,8 +1,14 @@
import { blockchainTests, expect } from '@0x/contracts-test-utils'; import { blockchainTests, expect, randomAddress } from '@0x/contracts-test-utils';
import { ZeroExRevertErrors } from '@0x/utils'; import { ZeroExRevertErrors } from '@0x/utils';
import { artifacts } from './artifacts'; import { artifacts } from './artifacts';
import { IBootstrapContract, IOwnableContract, TestInitialMigrationContract, ZeroExContract } from './wrappers'; import {
IBootstrapContract,
InitialMigrationContract,
IOwnableContract,
TestInitialMigrationContract,
ZeroExContract,
} from './wrappers';
blockchainTests.resets('Initial migration', env => { blockchainTests.resets('Initial migration', env => {
let owner: string; let owner: string;
@ -17,6 +23,7 @@ blockchainTests.resets('Initial migration', env => {
env.provider, env.provider,
env.txDefaults, env.txDefaults,
artifacts, artifacts,
env.txDefaults.from as string,
); );
bootstrapFeature = new IBootstrapContract( bootstrapFeature = new IBootstrapContract(
await migrator.bootstrapFeature().callAsync(), await migrator.bootstrapFeature().callAsync(),
@ -29,12 +36,40 @@ blockchainTests.resets('Initial migration', env => {
await deployCall.awaitTransactionSuccessAsync(); await deployCall.awaitTransactionSuccessAsync();
}); });
it('Self-destructs after deployment', async () => {
const dieRecipient = await migrator.dieRecipient().callAsync();
expect(dieRecipient).to.eq(owner);
});
it('Non-deployer cannot call deploy()', async () => {
const notDeployer = randomAddress();
const tx = migrator.deploy(owner).callAsync({ from: notDeployer });
return expect(tx).to.revertWith('InitialMigration/INVALID_SENDER');
});
it('External contract cannot call die()', async () => {
const _migrator = await InitialMigrationContract.deployFrom0xArtifactAsync(
artifacts.InitialMigration,
env.provider,
env.txDefaults,
artifacts,
env.txDefaults.from as string,
);
const tx = _migrator.die(owner).callAsync();
return expect(tx).to.revertWith('InitialMigration/INVALID_SENDER');
});
describe('bootstrapping', () => { describe('bootstrapping', () => {
it('Migrator cannot call bootstrap() again', async () => { it('Migrator cannot call bootstrap() again', async () => {
const tx = migrator.callBootstrap(zeroEx.address).awaitTransactionSuccessAsync(); const tx = migrator.callBootstrap(zeroEx.address).awaitTransactionSuccessAsync();
const selector = bootstrapFeature.getSelector('bootstrap'); const selector = bootstrapFeature.getSelector('bootstrap');
return expect(tx).to.revertWith(new ZeroExRevertErrors.Proxy.NotImplementedError(selector)); return expect(tx).to.revertWith(new ZeroExRevertErrors.Proxy.NotImplementedError(selector));
}); });
it('Bootstrap feature self destructs after deployment', async () => {
const codeSize = await migrator.getCodeSizeOf(bootstrapFeature.address).callAsync();
expect(codeSize).to.bignumber.eq(0);
});
}); });
describe('Ownable feature', () => { describe('Ownable feature', () => {
@ -49,9 +84,4 @@ blockchainTests.resets('Initial migration', env => {
expect(actualOwner).to.eq(owner); 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

@ -15,6 +15,7 @@ export async function initialMigrateAsync(
provider, provider,
txDefaults, txDefaults,
artifacts, artifacts,
txDefaults.from as string,
); );
const deployCall = migrator.deploy(owner); const deployCall = migrator.deploy(owner);
const zeroEx = new ZeroExContract(await deployCall.callAsync(), provider, {}); const zeroEx = new ZeroExContract(await deployCall.callAsync(), provider, {});