@0x/contracts-zero-ex: Add self-destructing to transformers

This commit is contained in:
Lawrence Forman
2020-05-20 14:42:09 -04:00
parent e1d213d1a3
commit 28402ff7d8
13 changed files with 306 additions and 25 deletions

View File

@@ -100,6 +100,36 @@ library LibTransformERC20RichErrors {
// Common Transformer errors ///////////////////////////////////////////////
function OnlyCallableByDeployerError(
address caller,
address deployer
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("OnlyCallableByDeployerError(address,address)")),
caller,
deployer
);
}
function InvalidExecutionContextError(
address actualContext,
address expectedContext
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("InvalidExecutionContextError(address,address)")),
actualContext,
expectedContext
);
}
function InvalidTransformDataError(
bytes memory transformData
)

View File

@@ -27,13 +27,13 @@ import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
import "../errors/LibTransformERC20RichErrors.sol";
import "../vendor/v3/IExchange.sol";
import "./IERC20Transformer.sol";
import "./Transformer.sol";
import "./LibERC20Transformer.sol";
/// @dev A transformer that fills an ERC20 market sell/buy quote.
contract FillQuoteTransformer is
IERC20Transformer
Transformer
{
/// @dev Transform data to ABI-encode and pass into `transform()`.
struct TransformData {
@@ -74,8 +74,6 @@ contract FillQuoteTransformer is
/// @dev The Exchange contract.
IExchange public immutable exchange;
/// @dev The nonce of the deployer when deploying this contract.
uint256 public immutable deploymentNonce;
/// @dev The ERC20Proxy address.
address public immutable erc20Proxy;
@@ -87,10 +85,12 @@ contract FillQuoteTransformer is
/// @dev Create this contract.
/// @param exchange_ The Exchange V3 instance.
/// @param deploymentNonce_ The nonce of the deployer when deploying this contract.
constructor(IExchange exchange_, uint256 deploymentNonce_) public {
constructor(IExchange exchange_, uint256 deploymentNonce_)
public
Transformer(deploymentNonce_)
{
exchange = exchange_;
erc20Proxy = exchange_.getAssetProxy(ERC20_ASSET_PROXY_ID);
deploymentNonce = deploymentNonce_;
}
/// @dev Sell this contract's entire balance of of `sellToken` in exchange
@@ -217,7 +217,7 @@ contract FillQuoteTransformer is
).rrevert();
}
}
return LibERC20Transformer.rlpEncodeNonce(deploymentNonce);
return _getRLPEncodedDeploymentNonce();
}
/// @dev Try to sell up to `sellAmount` from an order.

View File

@@ -24,14 +24,16 @@ import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
import "../errors/LibTransformERC20RichErrors.sol";
import "./IERC20Transformer.sol";
import "./Transformer.sol";
import "./LibERC20Transformer.sol";
/// @dev A transformer that transfers tokens to the taker.
contract PayTakerTransformer is
IERC20Transformer
Transformer
{
// solhint-disable no-empty-blocks
/// @dev Transform data to ABI-encode and pass into `transform()`.
struct TransformData {
// The tokens to transfer to the taker.
@@ -45,14 +47,13 @@ contract PayTakerTransformer is
using LibSafeMathV06 for uint256;
using LibERC20Transformer for IERC20TokenV06;
/// @dev The nonce of the deployer when deploying this contract.
uint256 public immutable deploymentNonce;
/// @dev Create this contract.
/// @param deploymentNonce_ The nonce of the deployer when deploying this contract.
constructor(uint256 deploymentNonce_) public {
deploymentNonce = deploymentNonce_;
}
/// @dev Construct the transformer and store the WETH address in an immutable.
constructor(uint256 deploymentNonce_)
public
Transformer(deploymentNonce_)
{}
/// @dev Forwards tokens to the taker.
/// @param taker The taker address (caller of `TransformERC20.transformERC20()`).
@@ -83,6 +84,6 @@ contract PayTakerTransformer is
data.tokens[i].transformerTransfer(taker, amount);
}
}
return LibERC20Transformer.rlpEncodeNonce(deploymentNonce);
return _getRLPEncodedDeploymentNonce();
}
}

View File

@@ -0,0 +1,80 @@
/*
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/LibTransformERC20RichErrors.sol";
import "./IERC20Transformer.sol";
import "./LibERC20Transformer.sol";
/// @dev Abstract base class for transformers.
abstract contract Transformer is
IERC20Transformer
{
using LibRichErrorsV06 for bytes;
/// @dev The address of the deployer.
address public immutable deployer;
/// @dev The nonce of the deployer when deploying this contract.
uint256 public immutable deploymentNonce;
/// @dev The original address of this contract.
address private immutable _implementation;
/// @dev Create this contract.
/// @param deploymentNonce_ The nonce of the deployer when deploying this contract.
constructor(uint256 deploymentNonce_) public {
deploymentNonce = deploymentNonce_;
deployer = msg.sender;
_implementation = address(this);
}
/// @dev Destruct this contract. Only callable by the deployer and will not
/// succeed in the context of a delegatecall (from another contract).
/// @param ethRecipient The recipient of ETH held in this contract.
function die(address payable ethRecipient)
external
virtual
{
// Only the deployer can call this.
if (msg.sender != deployer) {
LibTransformERC20RichErrors
.OnlyCallableByDeployerError(msg.sender, deployer)
.rrevert();
}
// Must be executing our own context.
if (address(this) != _implementation) {
LibTransformERC20RichErrors
.InvalidExecutionContextError(address(this), _implementation)
.rrevert();
}
selfdestruct(ethRecipient);
}
/// @dev Get the RLP-encoded deployment nonce of this contract.
/// @return rlpEncodedNonce The RLP-encoded deployment nonce.
function _getRLPEncodedDeploymentNonce()
internal
view
returns (bytes memory rlpEncodedNonce)
{
return LibERC20Transformer.rlpEncodeNonce(deploymentNonce);
}
}

View File

@@ -23,13 +23,13 @@ import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
import "../errors/LibTransformERC20RichErrors.sol";
import "./IERC20Transformer.sol";
import "./Transformer.sol";
import "./LibERC20Transformer.sol";
/// @dev A transformer that wraps or unwraps WETH.
contract WethTransformer is
IERC20Transformer
Transformer
{
/// @dev Transform data to ABI-encode and pass into `transform()`.
struct TransformData {
@@ -42,8 +42,6 @@ contract WethTransformer is
/// @dev The WETH contract address.
IEtherTokenV06 public immutable weth;
/// @dev The nonce of the deployer when deploying this contract.
uint256 public immutable deploymentNonce;
using LibRichErrorsV06 for bytes;
using LibSafeMathV06 for uint256;
@@ -53,9 +51,11 @@ contract WethTransformer is
/// @param weth_ The weth token.
/// @param deploymentNonce_ The nonce of the deployer when deploying this contract.
/// @dev Construct the transformer and store the WETH address in an immutable.
constructor(IEtherTokenV06 weth_, uint256 deploymentNonce_) public {
constructor(IEtherTokenV06 weth_, uint256 deploymentNonce_)
public
Transformer(deploymentNonce_)
{
weth = weth_;
deploymentNonce = deploymentNonce_;
}
/// @dev Wraps and unwraps WETH.
@@ -91,6 +91,6 @@ contract WethTransformer is
weth.withdraw(amount);
}
}
return LibERC20Transformer.rlpEncodeNonce(deploymentNonce);
return _getRLPEncodedDeploymentNonce();
}
}

View File

@@ -0,0 +1,37 @@
/*
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;
contract TestDelegateCaller {
function executeDelegateCall(
address target,
bytes calldata callData
)
external
{
(bool success, bytes memory resultData) = target.delegatecall(callData);
if (!success) {
assembly { revert(add(resultData, 32), mload(resultData)) }
}
assembly { return(add(resultData, 32), mload(resultData)) }
}
}

View File

@@ -0,0 +1,53 @@
/*
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/Transformer.sol";
contract TestTransformerBase is
Transformer
{
// solhint-disable no-empty-blocks
constructor(uint256 deploymentNonce_)
public
Transformer(deploymentNonce_)
{}
function transform(
bytes32,
address payable,
bytes calldata
)
external
override
returns (bytes memory rlpDeploymentNonce)
{
return hex"";
}
function getRLPEncodedDeploymentNonce()
external
view
returns (bytes memory)
{
return _getRLPEncodedDeploymentNonce();
}
}

View File

@@ -37,7 +37,7 @@ contract TestTransformerHost {
bytes calldata data
)
external
returns (bytes memory rlpDeploymentNonce)
returns (bytes memory)
{
(bool success, bytes memory resultData) =
address(transformer).delegatecall(abi.encodeWithSelector(

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|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|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|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|WethTransformer|ZeroEx).json"
},
"repository": {
"type": "git",

View File

@@ -43,6 +43,7 @@ import * as Ownable from '../test/generated-artifacts/Ownable.json';
import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json';
import * as SimpleFunctionRegistry from '../test/generated-artifacts/SimpleFunctionRegistry.json';
import * as TestCallTarget from '../test/generated-artifacts/TestCallTarget.json';
import * as TestDelegateCaller from '../test/generated-artifacts/TestDelegateCaller.json';
import * as TestFillQuoteTransformerExchange from '../test/generated-artifacts/TestFillQuoteTransformerExchange.json';
import * as TestFillQuoteTransformerHost from '../test/generated-artifacts/TestFillQuoteTransformerHost.json';
import * as TestFullMigration from '../test/generated-artifacts/TestFullMigration.json';
@@ -54,12 +55,14 @@ import * as TestSimpleFunctionRegistryFeatureImpl1 from '../test/generated-artif
import * as TestSimpleFunctionRegistryFeatureImpl2 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl2.json';
import * as TestTokenSpender from '../test/generated-artifacts/TestTokenSpender.json';
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 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';
import * as TestZeroExFeature from '../test/generated-artifacts/TestZeroExFeature.json';
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 WethTransformer from '../test/generated-artifacts/WethTransformer.json';
import * as ZeroEx from '../test/generated-artifacts/ZeroEx.json';
@@ -102,10 +105,12 @@ export const artifacts = {
IERC20Transformer: IERC20Transformer as ContractArtifact,
LibERC20Transformer: LibERC20Transformer as ContractArtifact,
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
Transformer: Transformer as ContractArtifact,
WethTransformer: WethTransformer as ContractArtifact,
IExchange: IExchange as ContractArtifact,
ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact,
TestCallTarget: TestCallTarget as ContractArtifact,
TestDelegateCaller: TestDelegateCaller as ContractArtifact,
TestFillQuoteTransformerExchange: TestFillQuoteTransformerExchange as ContractArtifact,
TestFillQuoteTransformerHost: TestFillQuoteTransformerHost as ContractArtifact,
TestFullMigration: TestFullMigration as ContractArtifact,
@@ -118,6 +123,7 @@ export const artifacts = {
TestTokenSpender: TestTokenSpender as ContractArtifact,
TestTokenSpenderERC20Token: TestTokenSpenderERC20Token as ContractArtifact,
TestTransformERC20: TestTransformERC20 as ContractArtifact,
TestTransformerBase: TestTransformerBase as ContractArtifact,
TestTransformerHost: TestTransformerHost as ContractArtifact,
TestWeth: TestWeth as ContractArtifact,
TestWethTransformerHost: TestWethTransformerHost as ContractArtifact,

View File

@@ -0,0 +1,68 @@
import { blockchainTests, constants, expect, randomAddress } from '@0x/contracts-test-utils';
import { BigNumber, ZeroExRevertErrors } from '@0x/utils';
import * as _ from 'lodash';
import { rlpEncodeNonce } from '../../src/nonce_utils';
import { artifacts } from '../artifacts';
import { TestDelegateCallerContract, TestTransformerBaseContract } from '../wrappers';
blockchainTests.resets('Transformer (base)', env => {
const deploymentNonce = _.random(0, 0xffffffff);
let deployer: string;
let delegateCaller: TestDelegateCallerContract;
let transformer: TestTransformerBaseContract;
before(async () => {
[deployer] = await env.getAccountAddressesAsync();
delegateCaller = await TestDelegateCallerContract.deployFrom0xArtifactAsync(
artifacts.TestDelegateCaller,
env.provider,
env.txDefaults,
artifacts,
);
transformer = await TestTransformerBaseContract.deployFrom0xArtifactAsync(
artifacts.TestTransformerBase,
env.provider,
{
...env.txDefaults,
from: deployer,
},
artifacts,
new BigNumber(deploymentNonce),
);
});
describe('_getRLPEncodedDeploymentNonce()', () => {
it('returns the RLP encoded deployment nonce', async () => {
const r = await transformer.getRLPEncodedDeploymentNonce().callAsync();
expect(r).to.eq(rlpEncodeNonce(deploymentNonce));
});
});
describe('die()', () => {
it('cannot be called by non-deployer', async () => {
const notDeployer = randomAddress();
const tx = transformer.die(randomAddress()).callAsync({ from: notDeployer });
return expect(tx).to.revertWith(
new ZeroExRevertErrors.TransformERC20.OnlyCallableByDeployerError(notDeployer, deployer),
);
});
it('cannot be called outside of its own context', async () => {
const callData = transformer.die(randomAddress()).getABIEncodedTransactionData();
const tx = delegateCaller.executeDelegateCall(transformer.address, callData).callAsync({ from: deployer });
return expect(tx).to.revertWith(
new ZeroExRevertErrors.TransformERC20.InvalidExecutionContextError(
delegateCaller.address,
transformer.address,
),
);
});
it('destroys the transformer', async () => {
await transformer.die(randomAddress()).awaitTransactionSuccessAsync({ from: deployer });
const code = await env.web3Wrapper.getContractCodeAsync(transformer.address);
return expect(code).to.eq(constants.NULL_BYTES);
});
});
});

View File

@@ -42,6 +42,7 @@ export * from '../test/generated-wrappers/ownable';
export * from '../test/generated-wrappers/pay_taker_transformer';
export * from '../test/generated-wrappers/simple_function_registry';
export * from '../test/generated-wrappers/test_call_target';
export * from '../test/generated-wrappers/test_delegate_caller';
export * from '../test/generated-wrappers/test_fill_quote_transformer_exchange';
export * from '../test/generated-wrappers/test_fill_quote_transformer_host';
export * from '../test/generated-wrappers/test_full_migration';
@@ -54,6 +55,7 @@ export * from '../test/generated-wrappers/test_simple_function_registry_feature_
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_host';
export * from '../test/generated-wrappers/test_weth';
export * from '../test/generated-wrappers/test_weth_transformer_host';
@@ -61,5 +63,6 @@ export * from '../test/generated-wrappers/test_zero_ex_feature';
export * from '../test/generated-wrappers/token_spender';
export * from '../test/generated-wrappers/token_spender_puppet';
export * from '../test/generated-wrappers/transform_erc20';
export * from '../test/generated-wrappers/transformer';
export * from '../test/generated-wrappers/weth_transformer';
export * from '../test/generated-wrappers/zero_ex';

View File

@@ -55,6 +55,7 @@
"test/generated-artifacts/PayTakerTransformer.json",
"test/generated-artifacts/SimpleFunctionRegistry.json",
"test/generated-artifacts/TestCallTarget.json",
"test/generated-artifacts/TestDelegateCaller.json",
"test/generated-artifacts/TestFillQuoteTransformerExchange.json",
"test/generated-artifacts/TestFillQuoteTransformerHost.json",
"test/generated-artifacts/TestFullMigration.json",
@@ -67,6 +68,7 @@
"test/generated-artifacts/TestTokenSpender.json",
"test/generated-artifacts/TestTokenSpenderERC20Token.json",
"test/generated-artifacts/TestTransformERC20.json",
"test/generated-artifacts/TestTransformerBase.json",
"test/generated-artifacts/TestTransformerHost.json",
"test/generated-artifacts/TestWeth.json",
"test/generated-artifacts/TestWethTransformerHost.json",
@@ -74,6 +76,7 @@
"test/generated-artifacts/TokenSpender.json",
"test/generated-artifacts/TokenSpenderPuppet.json",
"test/generated-artifacts/TransformERC20.json",
"test/generated-artifacts/Transformer.json",
"test/generated-artifacts/WethTransformer.json",
"test/generated-artifacts/ZeroEx.json"
],