Merge pull request #2592 from 0xProject/feat/zero-ex/transformer-deployer

Exchange Proxy: TransformerDeployer
This commit is contained in:
Lawrence Forman 2020-06-08 20:30:09 -04:00 committed by GitHub
commit f81697527c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 252 additions and 1 deletions

View File

@ -0,0 +1,82 @@
/*
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/AuthorizableV06.sol";
/// @dev A contract with a `die()` function.
interface IKillable {
function die() external;
}
/// @dev Deployer contract for ERC20 transformers.
/// Only authorities may call `deploy()` and `kill()`.
contract TransformerDeployer is
AuthorizableV06
{
/// @dev Emitted when a contract is deployed via `deploy()`.
/// @param deployedAddress The address of the deployed contract.
/// @param nonce The deployment nonce.
/// @param sender The caller of `deploy()`.
event Deployed(address deployedAddress, uint256 nonce, address sender);
/// @dev Emitted when a contract is killed via `kill()`.
/// @param target The address of the contract being killed..
/// @param sender The caller of `kill()`.
event Killed(address target, address sender);
// @dev The current nonce of this contract.
uint256 public nonce = 1;
// @dev Mapping of deployed contract address to deployment nonce.
mapping (address => uint256) public toDeploymentNonce;
/// @dev Create this contract and register authorities.
constructor(address[] memory authorities) public {
for (uint256 i = 0; i < authorities.length; ++i) {
_addAuthorizedAddress(authorities[i]);
}
}
/// @dev Deploy a new contract. Only callable by an authority.
/// Any attached ETH will also be forwarded.
function deploy(bytes memory bytecode)
public
payable
onlyAuthorized
returns (address deployedAddress)
{
uint256 deploymentNonce = nonce;
nonce += 1;
assembly {
deployedAddress := create(callvalue(), add(bytecode, 32), mload(bytecode))
}
toDeploymentNonce[deployedAddress] = deploymentNonce;
emit Deployed(deployedAddress, deploymentNonce, msg.sender);
}
/// @dev Call `die()` on a contract. Only callable by an authority.
function kill(IKillable target)
public
onlyAuthorized
{
target.die();
emit Killed(address(target), msg.sender);
}
}

View File

@ -0,0 +1,52 @@
/*
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/transformers/LibERC20Transformer.sol";
contract TestTransformerDeployerTransformer {
address payable public immutable deployer;
constructor() public payable {
deployer = msg.sender;
}
modifier onlyDeployer() {
require(msg.sender == deployer, "TestTransformerDeployerTransformer/ONLY_DEPLOYER");
_;
}
function die()
external
onlyDeployer
{
selfdestruct(deployer);
}
function isDeployedByDeployer(uint32 nonce)
external
view
returns (bool)
{
return LibERC20Transformer.getDeployedAddress(deployer, nonce) == address(this);
}
}

View File

@ -40,7 +40,7 @@
"config": {
"publicInterfaceContracts": "ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnable,ISimpleFunctionRegistry,ITokenSpender,ITransformERC20,FillQuoteTransformer,PayTakerTransformer,WethTransformer",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(AllowanceTarget|Bootstrap|FillQuoteTransformer|FixinCommon|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Transformer|IExchange|IFeature|IFlashWallet|IOwnable|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|Ownable|PayTakerTransformer|SimpleFunctionRegistry|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|WethTransformer|ZeroEx).json"
"abis": "./test/generated-artifacts/@(AllowanceTarget|Bootstrap|FillQuoteTransformer|FixinCommon|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Transformer|IExchange|IFeature|IFlashWallet|IOwnable|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|Ownable|PayTakerTransformer|SimpleFunctionRegistry|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json"
},
"repository": {
"type": "git",

View File

@ -57,6 +57,7 @@ import * as TestTokenSpender from '../test/generated-artifacts/TestTokenSpender.
import * as TestTokenSpenderERC20Token from '../test/generated-artifacts/TestTokenSpenderERC20Token.json';
import * as TestTransformerBase from '../test/generated-artifacts/TestTransformerBase.json';
import * as TestTransformERC20 from '../test/generated-artifacts/TestTransformERC20.json';
import * as TestTransformerDeployerTransformer from '../test/generated-artifacts/TestTransformerDeployerTransformer.json';
import * as TestTransformerHost from '../test/generated-artifacts/TestTransformerHost.json';
import * as TestWeth from '../test/generated-artifacts/TestWeth.json';
import * as TestWethTransformerHost from '../test/generated-artifacts/TestWethTransformerHost.json';
@ -64,6 +65,7 @@ import * as TestZeroExFeature from '../test/generated-artifacts/TestZeroExFeatur
import * as TokenSpender from '../test/generated-artifacts/TokenSpender.json';
import * as Transformer from '../test/generated-artifacts/Transformer.json';
import * as TransformERC20 from '../test/generated-artifacts/TransformERC20.json';
import * as TransformerDeployer from '../test/generated-artifacts/TransformerDeployer.json';
import * as WethTransformer from '../test/generated-artifacts/WethTransformer.json';
import * as ZeroEx from '../test/generated-artifacts/ZeroEx.json';
export const artifacts = {
@ -79,6 +81,7 @@ export const artifacts = {
FlashWallet: FlashWallet as ContractArtifact,
IAllowanceTarget: IAllowanceTarget as ContractArtifact,
IFlashWallet: IFlashWallet as ContractArtifact,
TransformerDeployer: TransformerDeployer as ContractArtifact,
Bootstrap: Bootstrap as ContractArtifact,
IBootstrap: IBootstrap as ContractArtifact,
IFeature: IFeature as ContractArtifact,
@ -124,6 +127,7 @@ export const artifacts = {
TestTokenSpenderERC20Token: TestTokenSpenderERC20Token as ContractArtifact,
TestTransformERC20: TestTransformERC20 as ContractArtifact,
TestTransformerBase: TestTransformerBase as ContractArtifact,
TestTransformerDeployerTransformer: TestTransformerDeployerTransformer as ContractArtifact,
TestTransformerHost: TestTransformerHost as ContractArtifact,
TestWeth: TestWeth as ContractArtifact,
TestWethTransformerHost: TestWethTransformerHost as ContractArtifact,

View File

@ -0,0 +1,109 @@
import { blockchainTests, constants, expect, randomAddress, verifyEventsFromLogs } from '@0x/contracts-test-utils';
import { AuthorizableRevertErrors, BigNumber } from '@0x/utils';
import { artifacts } from './artifacts';
import {
TestTransformerDeployerTransformerContract,
TransformerDeployerContract,
TransformerDeployerEvents,
} from './wrappers';
blockchainTests.resets('TransformerDeployer', env => {
let owner: string;
let authority: string;
let deployer: TransformerDeployerContract;
const deployBytes = artifacts.TestTransformerDeployerTransformer.compilerOutput.evm.bytecode.object;
before(async () => {
[owner, authority] = await env.getAccountAddressesAsync();
deployer = await TransformerDeployerContract.deployFrom0xArtifactAsync(
artifacts.TransformerDeployer,
env.provider,
env.txDefaults,
artifacts,
[authority],
);
});
describe('deploy()', () => {
it('non-authority cannot call', async () => {
const nonAuthority = randomAddress();
const tx = deployer.deploy(deployBytes).callAsync({ from: nonAuthority });
return expect(tx).to.revertWith(new AuthorizableRevertErrors.SenderNotAuthorizedError(nonAuthority));
});
it('authority can deploy', async () => {
const targetAddress = await deployer.deploy(deployBytes).callAsync({ from: authority });
const target = new TestTransformerDeployerTransformerContract(targetAddress, env.provider);
const receipt = await deployer.deploy(deployBytes).awaitTransactionSuccessAsync({ from: authority });
expect(await target.deployer().callAsync()).to.eq(deployer.address);
verifyEventsFromLogs(
receipt.logs,
[{ deployedAddress: targetAddress, nonce: new BigNumber(1), sender: authority }],
TransformerDeployerEvents.Deployed,
);
});
it('authority can deploy with value', async () => {
const targetAddress = await deployer.deploy(deployBytes).callAsync({ from: authority, value: 1 });
const target = new TestTransformerDeployerTransformerContract(targetAddress, env.provider);
const receipt = await deployer
.deploy(deployBytes)
.awaitTransactionSuccessAsync({ from: authority, value: 1 });
expect(await target.deployer().callAsync()).to.eq(deployer.address);
verifyEventsFromLogs(
receipt.logs,
[{ deployedAddress: targetAddress, nonce: new BigNumber(1), sender: authority }],
TransformerDeployerEvents.Deployed,
);
expect(await env.web3Wrapper.getBalanceInWeiAsync(targetAddress)).to.bignumber.eq(1);
});
it('updates nonce', async () => {
expect(await deployer.nonce().callAsync()).to.bignumber.eq(1);
await deployer.deploy(deployBytes).awaitTransactionSuccessAsync({ from: authority });
expect(await deployer.nonce().callAsync()).to.bignumber.eq(2);
});
it('nonce can predict deployment address', async () => {
const nonce = await deployer.nonce().callAsync();
const targetAddress = await deployer.deploy(deployBytes).callAsync({ from: authority });
const target = new TestTransformerDeployerTransformerContract(targetAddress, env.provider);
await deployer.deploy(deployBytes).awaitTransactionSuccessAsync({ from: authority });
expect(await target.isDeployedByDeployer(nonce).callAsync()).to.eq(true);
});
it('can retrieve deployment nonce from contract address', async () => {
const nonce = await deployer.nonce().callAsync();
const targetAddress = await deployer.deploy(deployBytes).callAsync({ from: authority });
await deployer.deploy(deployBytes).awaitTransactionSuccessAsync({ from: authority });
expect(await deployer.toDeploymentNonce(targetAddress).callAsync()).to.bignumber.eq(nonce);
});
});
describe('kill()', () => {
let target: TestTransformerDeployerTransformerContract;
before(async () => {
const targetAddress = await deployer.deploy(deployBytes).callAsync({ from: authority });
target = new TestTransformerDeployerTransformerContract(targetAddress, env.provider);
await deployer.deploy(deployBytes).awaitTransactionSuccessAsync({ from: authority });
});
it('authority cannot call', async () => {
const nonAuthority = randomAddress();
const tx = deployer.kill(target.address).callAsync({ from: nonAuthority });
return expect(tx).to.revertWith(new AuthorizableRevertErrors.SenderNotAuthorizedError(nonAuthority));
});
it('authority can kill a contract', async () => {
const receipt = await deployer.kill(target.address).awaitTransactionSuccessAsync({ from: authority });
verifyEventsFromLogs(
receipt.logs,
[{ target: target.address, sender: authority }],
TransformerDeployerEvents.Killed,
);
return expect(env.web3Wrapper.getContractCodeAsync(target.address)).to.become(constants.NULL_BYTES);
});
});
});

View File

@ -55,6 +55,7 @@ export * from '../test/generated-wrappers/test_token_spender';
export * from '../test/generated-wrappers/test_token_spender_erc20_token';
export * from '../test/generated-wrappers/test_transform_erc20';
export * from '../test/generated-wrappers/test_transformer_base';
export * from '../test/generated-wrappers/test_transformer_deployer_transformer';
export * from '../test/generated-wrappers/test_transformer_host';
export * from '../test/generated-wrappers/test_weth';
export * from '../test/generated-wrappers/test_weth_transformer_host';
@ -62,5 +63,6 @@ export * from '../test/generated-wrappers/test_zero_ex_feature';
export * from '../test/generated-wrappers/token_spender';
export * from '../test/generated-wrappers/transform_erc20';
export * from '../test/generated-wrappers/transformer';
export * from '../test/generated-wrappers/transformer_deployer';
export * from '../test/generated-wrappers/weth_transformer';
export * from '../test/generated-wrappers/zero_ex';

View File

@ -68,6 +68,7 @@
"test/generated-artifacts/TestTokenSpenderERC20Token.json",
"test/generated-artifacts/TestTransformERC20.json",
"test/generated-artifacts/TestTransformerBase.json",
"test/generated-artifacts/TestTransformerDeployerTransformer.json",
"test/generated-artifacts/TestTransformerHost.json",
"test/generated-artifacts/TestWeth.json",
"test/generated-artifacts/TestWethTransformerHost.json",
@ -75,6 +76,7 @@
"test/generated-artifacts/TokenSpender.json",
"test/generated-artifacts/TransformERC20.json",
"test/generated-artifacts/Transformer.json",
"test/generated-artifacts/TransformerDeployer.json",
"test/generated-artifacts/WethTransformer.json",
"test/generated-artifacts/ZeroEx.json"
],