@0x/contracts-zero-ex: Address audit feedback.

This commit is contained in:
Lawrence Forman 2020-06-04 16:15:38 -04:00
parent cb2cc05cac
commit ebfa62637e
26 changed files with 258 additions and 321 deletions

View File

@ -70,31 +70,20 @@ library LibTransformERC20RichErrors {
); );
} }
function UnauthorizedTransformerError( function TransformerFailedError(
address transformer, address transformer,
bytes memory rlpNonce bytes memory transformerData,
bytes memory resultData
) )
internal internal
pure pure
returns (bytes memory) returns (bytes memory)
{ {
return abi.encodeWithSelector( return abi.encodeWithSelector(
bytes4(keccak256("UnauthorizedTransformerError(address,bytes)")), bytes4(keccak256("TransformerFailedError(address,bytes,bytes)")),
transformer, transformer,
rlpNonce transformerData,
); resultData
}
function InvalidRLPNonceError(
bytes memory rlpNonce
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("InvalidRLPNonceError(bytes)")),
rlpNonce
); );
} }

View File

@ -42,7 +42,6 @@ contract AllowanceTarget is
bytes calldata callData bytes calldata callData
) )
external external
payable
override override
onlyAuthorized onlyAuthorized
returns (bytes memory resultData) returns (bytes memory resultData)

View File

@ -35,6 +35,5 @@ interface IAllowanceTarget is
bytes calldata callData bytes calldata callData
) )
external external
payable
returns (bytes memory resultData); returns (bytes memory resultData);
} }

View File

@ -29,9 +29,10 @@ interface ITransformERC20 {
/// @dev Defines a transformation to run in `transformERC20()`. /// @dev Defines a transformation to run in `transformERC20()`.
struct Transformation { struct Transformation {
// The transformation handler. // The deployment nonce for the transformer.
// Can receive the entire balance of `tokens`. // The address of the transformer contract will be derived from this
IERC20Transformer transformer; // value.
uint32 deploymentNonce;
// Arbitrary data to pass to the transformer. // Arbitrary data to pass to the transformer.
bytes data; bytes data;
} }
@ -52,6 +53,15 @@ interface ITransformERC20 {
uint256 outputTokenAmount uint256 outputTokenAmount
); );
/// @dev Raised when `setTransformerDeployer()` is called.
/// @param transformerDeployer The new deployer address.
event TransformerDeployerUpdated(address transformerDeployer);
/// @dev Replace the allowed deployer for transformers.
/// Only callable by the owner.
function setTransformerDeployer(address transformerDeployer)
external;
/// @dev Deploy a new flash wallet instance and replace the current one with it. /// @dev Deploy a new flash wallet instance and replace the current one with it.
/// Useful if we somehow break the current wallet instance. /// Useful if we somehow break the current wallet instance.
/// Anyone can call this. /// Anyone can call this.

View File

@ -44,13 +44,19 @@ contract TransformERC20 is
FixinCommon FixinCommon
{ {
/// @dev Stack vars for `_transformERC20Private()`.
struct TransformERC20PrivateState {
IFlashWallet wallet;
address transformerDeployer;
uint256 takerOutputTokenBalanceBefore;
uint256 takerOutputTokenBalanceAfter;
}
// solhint-disable // solhint-disable
/// @dev Name of this feature. /// @dev Name of this feature.
string public constant override FEATURE_NAME = "TransformERC20"; string public constant override FEATURE_NAME = "TransformERC20";
/// @dev Version of this feature. /// @dev Version of this feature.
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0); uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
/// @dev The trusted deployer for all transformers.
address public immutable transformDeployer;
/// @dev The implementation address of this feature. /// @dev The implementation address of this feature.
address private immutable _implementation; address private immutable _implementation;
// solhint-enable // solhint-enable
@ -58,37 +64,51 @@ contract TransformERC20 is
using LibSafeMathV06 for uint256; using LibSafeMathV06 for uint256;
using LibRichErrorsV06 for bytes; using LibRichErrorsV06 for bytes;
constructor(address trustedDeployer_) public { constructor() public {
_implementation = address(this); _implementation = address(this);
transformDeployer = trustedDeployer_;
} }
/// @dev Initialize and register this feature. /// @dev Initialize and register this feature.
/// Should be delegatecalled by `Migrate.migrate()`. /// Should be delegatecalled by `Migrate.migrate()`.
function migrate() external returns (bytes4 success) { /// @param transformerDeployer The trusted deployer for transformers.
function migrate(address transformerDeployer) external returns (bytes4 success) {
ISimpleFunctionRegistry(address(this)) ISimpleFunctionRegistry(address(this))
.extend(this.getTransformerDeployer.selector, _implementation); .extend(this.getTransformerDeployer.selector, _implementation);
ISimpleFunctionRegistry(address(this)) ISimpleFunctionRegistry(address(this))
.extend(this.createTransformWallet.selector, _implementation); .extend(this.createTransformWallet.selector, _implementation);
ISimpleFunctionRegistry(address(this)) ISimpleFunctionRegistry(address(this))
.extend(this.getTransformWallet.selector, _implementation); .extend(this.getTransformWallet.selector, _implementation);
ISimpleFunctionRegistry(address(this))
.extend(this.setTransformerDeployer.selector, _implementation);
ISimpleFunctionRegistry(address(this)) ISimpleFunctionRegistry(address(this))
.extend(this.transformERC20.selector, _implementation); .extend(this.transformERC20.selector, _implementation);
ISimpleFunctionRegistry(address(this)) ISimpleFunctionRegistry(address(this))
.extend(this._transformERC20.selector, _implementation); .extend(this._transformERC20.selector, _implementation);
createTransformWallet(); createTransformWallet();
LibTransformERC20Storage.getStorage().transformerDeployer = transformerDeployer;
return LibMigrate.MIGRATE_SUCCESS; return LibMigrate.MIGRATE_SUCCESS;
} }
/// @dev Replace the allowed deployer for transformers.
/// Only callable by the owner.
function setTransformerDeployer(address transformerDeployer)
external
override
onlyOwner
{
LibTransformERC20Storage.getStorage().transformerDeployer = transformerDeployer;
emit TransformerDeployerUpdated(transformerDeployer);
}
/// @dev Return the allowed deployer for transformers. /// @dev Return the allowed deployer for transformers.
/// @return deployer The transform deployer address. /// @return deployer The transform deployer address.
function getTransformerDeployer() function getTransformerDeployer()
external public
override override
view view
returns (address deployer) returns (address deployer)
{ {
return transformDeployer; return LibTransformERC20Storage.getStorage().transformerDeployer;
} }
/// @dev Deploy a new wallet instance and replace the current one with it. /// @dev Deploy a new wallet instance and replace the current one with it.
@ -209,8 +229,7 @@ contract TransformERC20 is
uint256 minOutputTokenAmount, uint256 minOutputTokenAmount,
Transformation[] memory transformations Transformation[] memory transformations
) )
public private
payable
returns (uint256 outputTokenAmount) returns (uint256 outputTokenAmount)
{ {
// If the input token amount is -1, transform the taker's entire // If the input token amount is -1, transform the taker's entire
@ -220,31 +239,44 @@ contract TransformERC20 is
.getSpendableERC20BalanceOf(inputToken, taker); .getSpendableERC20BalanceOf(inputToken, taker);
} }
IFlashWallet wallet = getTransformWallet(); TransformERC20PrivateState memory state;
state.wallet = getTransformWallet();
state.transformerDeployer = getTransformerDeployer();
// Remember the initial output token balance of the taker. // Remember the initial output token balance of the taker.
uint256 takerOutputTokenBalanceBefore = state.takerOutputTokenBalanceBefore =
LibERC20Transformer.getTokenBalanceOf(outputToken, taker); LibERC20Transformer.getTokenBalanceOf(outputToken, taker);
// Pull input tokens from the taker to the wallet and transfer attached ETH. // Pull input tokens from the taker to the wallet and transfer attached ETH.
_transferInputTokensAndAttachedEth(inputToken, taker, address(wallet), inputTokenAmount); _transferInputTokensAndAttachedEth(
inputToken,
taker,
address(state.wallet),
inputTokenAmount
);
// Perform transformations. // Perform transformations.
for (uint256 i = 0; i < transformations.length; ++i) { for (uint256 i = 0; i < transformations.length; ++i) {
_executeTransformation(wallet, transformations[i], taker, callDataHash); _executeTransformation(
state.wallet,
transformations[i],
state.transformerDeployer,
taker,
callDataHash
);
} }
// Compute how much output token has been transferred to the taker. // Compute how much output token has been transferred to the taker.
uint256 takerOutputTokenBalanceAfter = state.takerOutputTokenBalanceAfter =
LibERC20Transformer.getTokenBalanceOf(outputToken, taker); LibERC20Transformer.getTokenBalanceOf(outputToken, taker);
if (takerOutputTokenBalanceAfter > takerOutputTokenBalanceBefore) { if (state.takerOutputTokenBalanceAfter > state.takerOutputTokenBalanceBefore) {
outputTokenAmount = takerOutputTokenBalanceAfter.safeSub( outputTokenAmount = state.takerOutputTokenBalanceAfter.safeSub(
takerOutputTokenBalanceBefore state.takerOutputTokenBalanceBefore
); );
} else if (takerOutputTokenBalanceAfter < takerOutputTokenBalanceBefore) { } else if (state.takerOutputTokenBalanceAfter < state.takerOutputTokenBalanceBefore) {
LibTransformERC20RichErrors.NegativeTransformERC20OutputError( LibTransformERC20RichErrors.NegativeTransformERC20OutputError(
address(outputToken), address(outputToken),
takerOutputTokenBalanceBefore - takerOutputTokenBalanceAfter state.takerOutputTokenBalanceBefore - state.takerOutputTokenBalanceAfter
).rrevert(); ).rrevert();
} }
// Ensure enough output token has been sent to the taker. // Ensure enough output token has been sent to the taker.
@ -316,20 +348,27 @@ contract TransformERC20 is
/// @dev Executs a transformer in the context of `wallet`. /// @dev Executs a transformer in the context of `wallet`.
/// @param wallet The wallet instance. /// @param wallet The wallet instance.
/// @param transformation The transformation. /// @param transformation The transformation.
/// @param transformerDeployer The address of the transformer deployer.
/// @param taker The taker address. /// @param taker The taker address.
/// @param callDataHash Hash of the calldata. /// @param callDataHash Hash of the calldata.
function _executeTransformation( function _executeTransformation(
IFlashWallet wallet, IFlashWallet wallet,
Transformation memory transformation, Transformation memory transformation,
address transformerDeployer,
address payable taker, address payable taker,
bytes32 callDataHash bytes32 callDataHash
) )
private private
{ {
// Derive the transformer address from the deployment nonce.
address payable transformer = LibERC20Transformer.getDeployedAddress(
transformerDeployer,
transformation.deploymentNonce
);
// Call `transformer.transform()` as the wallet. // Call `transformer.transform()` as the wallet.
bytes memory resultData = wallet.executeDelegateCall( bytes memory resultData = wallet.executeDelegateCall(
// Call target. // The call target.
address(uint160(address(transformation.transformer))), transformer,
// Call data. // Call data.
abi.encodeWithSelector( abi.encodeWithSelector(
IERC20Transformer.transform.selector, IERC20Transformer.transform.selector,
@ -338,39 +377,15 @@ contract TransformERC20 is
transformation.data transformation.data
) )
); );
// Ensure the transformer returned its valid rlp-encoded deployment nonce. // Ensure the transformer returned the magic bytes.
bytes memory rlpNonce = resultData.length == 0 if (resultData.length != 32 ||
? new bytes(0) abi.decode(resultData, (bytes4)) != LibERC20Transformer.TRANSFORMER_SUCCESS
: abi.decode(resultData, (bytes)); ) {
if (_getExpectedDeployment(rlpNonce) != address(transformation.transformer)) { LibTransformERC20RichErrors.TransformerFailedError(
LibTransformERC20RichErrors.UnauthorizedTransformerError( transformer,
address(transformation.transformer), transformation.data,
rlpNonce resultData
).rrevert(); ).rrevert();
} }
} }
/// @dev Compute the expected deployment address by `transformDeployer` at
/// the nonce given by `rlpNonce`.
/// @param rlpNonce The RLP-encoded nonce that
/// the deployer had when deploying a contract.
/// @return deploymentAddress The deployment address.
function _getExpectedDeployment(bytes memory rlpNonce)
private
view
returns (address deploymentAddress)
{
// See https://github.com/ethereum/wiki/wiki/RLP for RLP encoding rules.
// The RLP-encoded nonce may be prefixed with a length byte.
// We only support nonces up to 32-bits.
if (rlpNonce.length == 0 || rlpNonce.length > 5) {
LibTransformERC20RichErrors.InvalidRLPNonceError(rlpNonce).rrevert();
}
return address(uint160(uint256(keccak256(abi.encodePacked(
byte(uint8(0xC0 + 21 + rlpNonce.length)),
byte(uint8(0x80 + 20)),
transformDeployer,
rlpNonce
)))));
}
} }

View File

@ -40,6 +40,11 @@ contract FullMigration {
TransformERC20 transformERC20; TransformERC20 transformERC20;
} }
/// @dev Parameters needed to initialize features.
struct MigrateOpts {
address transformerDeployer;
}
/// @dev The allowed caller of `deploy()`. /// @dev The allowed caller of `deploy()`.
address public immutable deployer; address public immutable deployer;
/// @dev The initial migration contract. /// @dev The initial migration contract.
@ -62,9 +67,11 @@ contract FullMigration {
/// @param owner The owner of the contract. /// @param owner The owner of the contract.
/// @param features Features to add to the proxy. /// @param features Features to add to the proxy.
/// @return zeroEx The deployed and configured `ZeroEx` contract. /// @return zeroEx The deployed and configured `ZeroEx` contract.
/// @param migrateOpts Parameters needed to initialize features.
function deploy( function deploy(
address payable owner, address payable owner,
Features memory features Features memory features,
MigrateOpts memory migrateOpts
) )
public public
returns (ZeroEx zeroEx) returns (ZeroEx zeroEx)
@ -81,7 +88,7 @@ contract FullMigration {
); );
// Add features. // Add features.
_addFeatures(zeroEx, owner, features); _addFeatures(zeroEx, owner, features, migrateOpts);
// Transfer ownership to the real owner. // Transfer ownership to the real owner.
IOwnable(address(zeroEx)).transferOwnership(owner); IOwnable(address(zeroEx)).transferOwnership(owner);
@ -106,10 +113,12 @@ contract FullMigration {
/// @param zeroEx The bootstrapped ZeroEx contract. /// @param zeroEx The bootstrapped ZeroEx contract.
/// @param owner The ultimate owner of the ZeroEx contract. /// @param owner The ultimate owner of the ZeroEx contract.
/// @param features Features to add to the proxy. /// @param features Features to add to the proxy.
/// @param migrateOpts Parameters needed to initialize features.
function _addFeatures( function _addFeatures(
ZeroEx zeroEx, ZeroEx zeroEx,
address owner, address owner,
Features memory features Features memory features,
MigrateOpts memory migrateOpts
) )
private private
{ {
@ -138,7 +147,8 @@ contract FullMigration {
ownable.migrate( ownable.migrate(
address(features.transformERC20), address(features.transformERC20),
abi.encodeWithSelector( abi.encodeWithSelector(
TransformERC20.migrate.selector TransformERC20.migrate.selector,
migrateOpts.transformerDeployer
), ),
address(this) address(this)
); );

View File

@ -30,6 +30,8 @@ library LibTransformERC20Storage {
struct Storage { struct Storage {
// The current wallet instance. // The current wallet instance.
IFlashWallet wallet; IFlashWallet wallet;
// The transformer deployer address.
address transformerDeployer;
} }
/// @dev Get the storage bucket for this contract. /// @dev Get the storage bucket for this contract.

View File

@ -93,10 +93,9 @@ contract FillQuoteTransformer is
/// @dev Create this contract. /// @dev Create this contract.
/// @param exchange_ The Exchange V3 instance. /// @param exchange_ The Exchange V3 instance.
/// @param deploymentNonce_ The nonce of the deployer when deploying this contract. constructor(IExchange exchange_)
constructor(IExchange exchange_, uint256 deploymentNonce_)
public public
Transformer(deploymentNonce_) Transformer()
{ {
exchange = exchange_; exchange = exchange_;
erc20Proxy = exchange_.getAssetProxy(ERC20_ASSET_PROXY_ID); erc20Proxy = exchange_.getAssetProxy(ERC20_ASSET_PROXY_ID);
@ -106,9 +105,7 @@ contract FillQuoteTransformer is
/// for `buyToken` by filling `orders`. Protocol fees should be attached /// for `buyToken` by filling `orders`. Protocol fees should be attached
/// to this call. `buyToken` and excess ETH will be transferred back to the caller. /// to this call. `buyToken` and excess ETH will be transferred back to the caller.
/// @param data_ ABI-encoded `TransformData`. /// @param data_ ABI-encoded `TransformData`.
/// @return rlpDeploymentNonce RLP-encoded deployment nonce of the deployer /// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`).
/// when this transformer was deployed. This is used to verify that
/// this transformer was deployed by a trusted contract.
function transform( function transform(
bytes32, // callDataHash, bytes32, // callDataHash,
address payable, // taker, address payable, // taker,
@ -116,7 +113,7 @@ contract FillQuoteTransformer is
) )
external external
override override
returns (bytes memory rlpDeploymentNonce) returns (bytes4 success)
{ {
TransformData memory data = abi.decode(data_, (TransformData)); TransformData memory data = abi.decode(data_, (TransformData));
@ -231,7 +228,7 @@ contract FillQuoteTransformer is
).rrevert(); ).rrevert();
} }
} }
return _getRLPEncodedDeploymentNonce(); return LibERC20Transformer.TRANSFORMER_SUCCESS;
} }
/// @dev Try to sell up to `sellAmount` from an order. /// @dev Try to sell up to `sellAmount` from an order.

View File

@ -30,14 +30,12 @@ interface IERC20Transformer {
/// @param callDataHash The hash of the `TransformERC20.transformERC20()` calldata. /// @param callDataHash The hash of the `TransformERC20.transformERC20()` calldata.
/// @param taker The taker address (caller of `TransformERC20.transformERC20()`). /// @param taker The taker address (caller of `TransformERC20.transformERC20()`).
/// @param data Arbitrary data to pass to the transformer. /// @param data Arbitrary data to pass to the transformer.
/// @return rlpDeploymentNonce RLP-encoded deployment nonce of the deployer /// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`).
/// when this transformer was deployed. This is used to verify that
/// this transformer was deployed by a trusted contract.
function transform( function transform(
bytes32 callDataHash, bytes32 callDataHash,
address payable taker, address payable taker,
bytes calldata data bytes calldata data
) )
external external
returns (bytes memory rlpDeploymentNonce); returns (bytes4 success);
} }

View File

@ -29,6 +29,9 @@ library LibERC20Transformer {
/// @dev ETH pseudo-token address. /// @dev ETH pseudo-token address.
address constant internal ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address constant internal ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @dev Return value indicating success in `IERC20Transformer.transform()`.
/// This is just `keccak256('TRANSFORMER_SUCCESS')`.
bytes4 constant internal TRANSFORMER_SUCCESS = 0x13c9929e;
/// @dev Transfer ERC20 tokens and ETH. /// @dev Transfer ERC20 tokens and ETH.
/// @param token An ERC20 or the ETH pseudo-token address (`ETH_TOKEN_ADDRESS`). /// @param token An ERC20 or the ETH pseudo-token address (`ETH_TOKEN_ADDRESS`).
@ -68,17 +71,21 @@ library LibERC20Transformer {
view view
returns (uint256 tokenBalance) returns (uint256 tokenBalance)
{ {
return isTokenETH(token) ? owner.balance : token.balanceOf(owner); if (isTokenETH(token)) {
return owner.balance
}
return token.balanceOf(owner);
} }
/// @dev RLP-encode a 32-bit or less account nonce. /// @dev RLP-encode a 32-bit or less account nonce.
/// @param nonce A positive integer in the range 0 <= nonce < 2^32. /// @param nonce A positive integer in the range 0 <= nonce < 2^32.
/// @return rlpNonce The RLP encoding. /// @return rlpNonce The RLP encoding.
function rlpEncodeNonce(uint256 nonce) function rlpEncodeNonce(uint32 nonce)
internal internal
pure pure
returns (bytes memory rlpNonce) returns (bytes memory rlpNonce)
{ {
// See https://github.com/ethereum/wiki/wiki/RLP for RLP encoding rules.
if (nonce == 0) { if (nonce == 0) {
rlpNonce = new bytes(1); rlpNonce = new bytes(1);
rlpNonce[0] = 0x80; rlpNonce[0] = 0x80;
@ -100,15 +107,36 @@ library LibERC20Transformer {
rlpNonce[1] = byte(uint8((nonce & 0xFF0000) >> 16)); rlpNonce[1] = byte(uint8((nonce & 0xFF0000) >> 16));
rlpNonce[2] = byte(uint8((nonce & 0xFF00) >> 8)); rlpNonce[2] = byte(uint8((nonce & 0xFF00) >> 8));
rlpNonce[3] = byte(uint8(nonce)); rlpNonce[3] = byte(uint8(nonce));
} else if (nonce <= 0xFFFFFFFF) { } else {
rlpNonce = new bytes(5); rlpNonce = new bytes(5);
rlpNonce[0] = 0x84; rlpNonce[0] = 0x84;
rlpNonce[1] = byte(uint8((nonce & 0xFF000000) >> 24)); rlpNonce[1] = byte(uint8((nonce & 0xFF000000) >> 24));
rlpNonce[2] = byte(uint8((nonce & 0xFF0000) >> 16)); rlpNonce[2] = byte(uint8((nonce & 0xFF0000) >> 16));
rlpNonce[3] = byte(uint8((nonce & 0xFF00) >> 8)); rlpNonce[3] = byte(uint8((nonce & 0xFF00) >> 8));
rlpNonce[4] = byte(uint8(nonce)); rlpNonce[4] = byte(uint8(nonce));
} else {
revert("LibERC20Transformer/INVALID_ENCODE_NONCE");
} }
} }
/// @dev Compute the expected deployment address by `deployer` at
/// the nonce given by `deploymentNonce`.
/// @param deployer The address of the deployer.
/// @param deploymentNonce The nonce that the deployer had when deploying
/// a contract.
/// @return deploymentAddress The deployment address.
function getDeployedAddress(address deployer, uint32 deploymentNonce)
internal
pure
returns (address payable deploymentAddress)
{
// The address of if a deployed contract is the lower 20 bytes of the
// hash of the RLP-encoded deployer's account address + account nonce.
// See: https://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed
bytes memory rlpNonce = rlpEncodeNonce(deploymentNonce);
return address(uint160(uint256(keccak256(abi.encodePacked(
byte(uint8(0xC0 + 21 + rlpNonce.length)),
byte(uint8(0x80 + 20)),
deployer,
rlpNonce
)))));
}
} }

View File

@ -50,18 +50,15 @@ contract PayTakerTransformer is
uint256 private constant MAX_UINT256 = uint256(-1); uint256 private constant MAX_UINT256 = uint256(-1);
/// @dev Create this contract. /// @dev Create this contract.
/// @param deploymentNonce_ The nonce of the deployer when deploying this contract. constructor()
constructor(uint256 deploymentNonce_)
public public
Transformer(deploymentNonce_) Transformer()
{} {}
/// @dev Forwards tokens to the taker. /// @dev Forwards tokens to the taker.
/// @param taker The taker address (caller of `TransformERC20.transformERC20()`). /// @param taker The taker address (caller of `TransformERC20.transformERC20()`).
/// @param data_ ABI-encoded `TransformData`, indicating which tokens to transfer. /// @param data_ ABI-encoded `TransformData`, indicating which tokens to transfer.
/// @return rlpDeploymentNonce RLP-encoded deployment nonce of the deployer /// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`).
/// when this transformer was deployed. This is used to verify that
/// this transformer was deployed by a trusted contract.
function transform( function transform(
bytes32, // callDataHash, bytes32, // callDataHash,
address payable taker, address payable taker,
@ -69,7 +66,7 @@ contract PayTakerTransformer is
) )
external external
override override
returns (bytes memory rlpDeploymentNonce) returns (bytes4 success)
{ {
TransformData memory data = abi.decode(data_, (TransformData)); TransformData memory data = abi.decode(data_, (TransformData));
@ -85,6 +82,6 @@ contract PayTakerTransformer is
data.tokens[i].transformerTransfer(taker, amount); data.tokens[i].transformerTransfer(taker, amount);
} }
} }
return _getRLPEncodedDeploymentNonce(); return LibERC20Transformer.TRANSFORMER_SUCCESS;
} }
} }

View File

@ -22,7 +22,6 @@ pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
import "../errors/LibTransformERC20RichErrors.sol"; import "../errors/LibTransformERC20RichErrors.sol";
import "./IERC20Transformer.sol"; import "./IERC20Transformer.sol";
import "./LibERC20Transformer.sol";
/// @dev Abstract base class for transformers. /// @dev Abstract base class for transformers.
@ -33,15 +32,11 @@ abstract contract Transformer is
/// @dev The address of the deployer. /// @dev The address of the deployer.
address public immutable 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. /// @dev The original address of this contract.
address private immutable _implementation; address private immutable _implementation;
/// @dev Create this contract. /// @dev Create this contract.
/// @param deploymentNonce_ The nonce of the deployer when deploying this contract. constructor() public {
constructor(uint256 deploymentNonce_) public {
deploymentNonce = deploymentNonce_;
deployer = msg.sender; deployer = msg.sender;
_implementation = address(this); _implementation = address(this);
} }
@ -67,14 +62,4 @@ abstract contract Transformer is
} }
selfdestruct(ethRecipient); 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

@ -51,19 +51,16 @@ contract WethTransformer is
/// @dev Construct the transformer and store the WETH address in an immutable. /// @dev Construct the transformer and store the WETH address in an immutable.
/// @param weth_ The weth token. /// @param weth_ The weth token.
/// @param deploymentNonce_ The nonce of the deployer when deploying this contract. constructor(IEtherTokenV06 weth_)
constructor(IEtherTokenV06 weth_, uint256 deploymentNonce_)
public public
Transformer(deploymentNonce_) Transformer()
{ {
weth = weth_; weth = weth_;
} }
/// @dev Wraps and unwraps WETH. /// @dev Wraps and unwraps WETH.
/// @param data_ ABI-encoded `TransformData`, indicating which token to wrap/umwrap. /// @param data_ ABI-encoded `TransformData`, indicating which token to wrap/umwrap.
/// @return rlpDeploymentNonce RLP-encoded deployment nonce of the deployer /// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`).
/// when this transformer was deployed. This is used to verify that
/// this transformer was deployed by a trusted contract.
function transform( function transform(
bytes32, // callDataHash, bytes32, // callDataHash,
address payable, // taker, address payable, // taker,
@ -71,7 +68,7 @@ contract WethTransformer is
) )
external external
override override
returns (bytes memory rlpDeploymentNonce) returns (bytes4 success)
{ {
TransformData memory data = abi.decode(data_, (TransformData)); TransformData memory data = abi.decode(data_, (TransformData));
if (!data.token.isTokenETH() && data.token != weth) { if (!data.token.isTokenETH() && data.token != weth) {
@ -95,6 +92,6 @@ contract WethTransformer is
weth.withdraw(amount); weth.withdraw(amount);
} }
} }
return _getRLPEncodedDeploymentNonce(); return LibERC20Transformer.TRANSFORMER_SUCCESS;
} }
} }

View File

@ -35,12 +35,11 @@ contract TestFillQuoteTransformerHost is
) )
external external
payable payable
returns (bytes memory rlpDeploymentNonce)
{ {
if (inputTokenAmount != 0) { if (inputTokenAmount != 0) {
inputToken.mint(address(this), inputTokenAmount); inputToken.mint(address(this), inputTokenAmount);
} }
// Have to make this call externally because transformers aren't payable. // Have to make this call externally because transformers aren't payable.
return this.rawExecuteTransform(transformer, bytes32(0), msg.sender, data); this.rawExecuteTransform(transformer, bytes32(0), msg.sender, data);
} }
} }

View File

@ -34,7 +34,6 @@ contract TestMintTokenERC20Transformer is
uint256 burnAmount; uint256 burnAmount;
uint256 mintAmount; uint256 mintAmount;
uint256 feeAmount; uint256 feeAmount;
bytes deploymentNonce;
} }
event MintTransform( event MintTransform(
@ -54,7 +53,7 @@ contract TestMintTokenERC20Transformer is
) )
external external
override override
returns (bytes memory rlpDeploymentNonce) returns (bytes4 success)
{ {
TransformData memory data = abi.decode(data_, (TransformData)); TransformData memory data = abi.decode(data_, (TransformData));
emit MintTransform( emit MintTransform(
@ -79,6 +78,6 @@ contract TestMintTokenERC20Transformer is
// Burn fees from output. // Burn fees from output.
data.outputToken.burn(taker, data.feeAmount); data.outputToken.burn(taker, data.feeAmount);
} }
return data.deploymentNonce; return LibERC20Transformer.TRANSFORMER_SUCCESS;
} }
} }

View File

@ -26,8 +26,8 @@ contract TestTransformERC20 is
TransformERC20 TransformERC20
{ {
// solhint-disable no-empty-blocks // solhint-disable no-empty-blocks
constructor(address trustedDeployer) constructor()
TransformERC20(trustedDeployer) TransformERC20()
public public
{} {}

View File

@ -20,17 +20,12 @@ pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
import "../src/transformers/Transformer.sol"; import "../src/transformers/Transformer.sol";
import "../src/transformers/LibERC20Transformer.sol";
contract TestTransformerBase is contract TestTransformerBase is
Transformer Transformer
{ {
// solhint-disable no-empty-blocks
constructor(uint256 deploymentNonce_)
public
Transformer(deploymentNonce_)
{}
function transform( function transform(
bytes32, bytes32,
address payable, address payable,
@ -38,16 +33,8 @@ contract TestTransformerBase is
) )
external external
override override
returns (bytes memory rlpDeploymentNonce) returns (bytes4 success)
{ {
return hex""; return LibERC20Transformer.TRANSFORMER_SUCCESS;
}
function getRLPEncodedDeploymentNonce()
external
view
returns (bytes memory)
{
return _getRLPEncodedDeploymentNonce();
} }
} }

View File

@ -37,19 +37,21 @@ contract TestTransformerHost {
bytes calldata data bytes calldata data
) )
external external
returns (bytes memory)
{ {
(bool success, bytes memory resultData) = (bool _success, bytes memory resultData) =
address(transformer).delegatecall(abi.encodeWithSelector( address(transformer).delegatecall(abi.encodeWithSelector(
transformer.transform.selector, transformer.transform.selector,
callDataHash, callDataHash,
taker, taker,
data data
)); ));
if (!success) { if (!_success) {
resultData.rrevert(); resultData.rrevert();
} }
assembly { return(add(resultData, 32), mload(resultData)) } require(
abi.decode(resultData, (bytes4)) == LibERC20Transformer.TRANSFORMER_SUCCESS,
"TestTransformerHost/INVALID_TRANSFORMER_RESULT"
);
} }
// solhint-disable // solhint-disable

View File

@ -43,12 +43,11 @@ contract TestWethTransformerHost is
) )
external external
payable payable
returns (bytes memory rlpDeploymentNonce)
{ {
if (wethAmount != 0) { if (wethAmount != 0) {
_weth.deposit{value: wethAmount}(); _weth.deposit{value: wethAmount}();
} }
// Have to make this call externally because transformers aren't payable. // Have to make this call externally because transformers aren't payable.
return this.rawExecuteTransform(transformer, bytes32(0), msg.sender, data); this.rawExecuteTransform(transformer, bytes32(0), msg.sender, data);
} }
} }

View File

@ -8,10 +8,9 @@ import {
randomAddress, randomAddress,
verifyEventsFromLogs, verifyEventsFromLogs,
} from '@0x/contracts-test-utils'; } from '@0x/contracts-test-utils';
import { AbiEncoder, hexUtils, ZeroExRevertErrors } from '@0x/utils'; import { AbiEncoder, hexUtils, OwnableRevertErrors, ZeroExRevertErrors } from '@0x/utils';
import { ETH_TOKEN_ADDRESS } from '../../src/constants'; import { ETH_TOKEN_ADDRESS } from '../../src/constants';
import { getRLPEncodedAccountNonceAsync } from '../../src/nonce_utils';
import { artifacts } from '../artifacts'; import { artifacts } from '../artifacts';
import { abis } from '../utils/abis'; import { abis } from '../utils/abis';
import { fullMigrateAsync } from '../utils/migration'; import { fullMigrateAsync } from '../utils/migration';
@ -27,6 +26,7 @@ import {
} from '../wrappers'; } from '../wrappers';
blockchainTests.resets('TransformERC20 feature', env => { blockchainTests.resets('TransformERC20 feature', env => {
let owner: string;
let taker: string; let taker: string;
let transformerDeployer: string; let transformerDeployer: string;
let zeroEx: ZeroExContract; let zeroEx: ZeroExContract;
@ -35,17 +35,21 @@ blockchainTests.resets('TransformERC20 feature', env => {
let allowanceTarget: string; let allowanceTarget: string;
before(async () => { before(async () => {
let owner;
[owner, taker, transformerDeployer] = await env.getAccountAddressesAsync(); [owner, taker, transformerDeployer] = await env.getAccountAddressesAsync();
zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults, { zeroEx = await fullMigrateAsync(
owner,
env.provider,
env.txDefaults,
{
transformERC20: await TransformERC20Contract.deployFrom0xArtifactAsync( transformERC20: await TransformERC20Contract.deployFrom0xArtifactAsync(
artifacts.TestTransformERC20, artifacts.TestTransformERC20,
env.provider, env.provider,
env.txDefaults, env.txDefaults,
artifacts, artifacts,
transformerDeployer,
), ),
}); },
{ transformerDeployer },
);
feature = new TransformERC20Contract(zeroEx.address, env.provider, env.txDefaults, abis); feature = new TransformERC20Contract(zeroEx.address, env.provider, env.txDefaults, abis);
wallet = new FlashWalletContract(await feature.getTransformWallet().callAsync(), env.provider, env.txDefaults); wallet = new FlashWalletContract(await feature.getTransformWallet().callAsync(), env.provider, env.txDefaults);
allowanceTarget = await new ITokenSpenderContract(zeroEx.address, env.provider, env.txDefaults) allowanceTarget = await new ITokenSpenderContract(zeroEx.address, env.provider, env.txDefaults)
@ -53,7 +57,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
.callAsync(); .callAsync();
}); });
const { MAX_UINT256, NULL_BYTES, ZERO_AMOUNT } = constants; const { MAX_UINT256, ZERO_AMOUNT } = constants;
describe('wallets', () => { describe('wallets', () => {
it('createTransformWallet() replaces the current wallet', async () => { it('createTransformWallet() replaces the current wallet', async () => {
@ -64,11 +68,39 @@ blockchainTests.resets('TransformERC20 feature', env => {
}); });
}); });
describe('transformer deployer', () => {
it('`getTransformerDeployer()` returns the transformer deployer', async () => {
const actualDeployer = await feature.getTransformerDeployer().callAsync();
expect(actualDeployer).to.eq(transformerDeployer);
});
it('owner can set the transformer deployer with `setTransformerDeployer()`', async () => {
const newDeployer = randomAddress();
const receipt = await feature
.setTransformerDeployer(newDeployer)
.awaitTransactionSuccessAsync({ from: owner });
verifyEventsFromLogs(
receipt.logs,
[{ transformerDeployer: newDeployer }],
TransformERC20Events.TransformerDeployerUpdated,
);
const actualDeployer = await feature.getTransformerDeployer().callAsync();
expect(actualDeployer).to.eq(newDeployer);
});
it('non-owner cannot set the transformer deployer with `setTransformerDeployer()`', async () => {
const newDeployer = randomAddress();
const notOwner = randomAddress();
const tx = feature.setTransformerDeployer(newDeployer).callAsync({ from: notOwner });
return expect(tx).to.revertWith(new OwnableRevertErrors.OnlyOwnerError(notOwner, owner));
});
});
describe('_transformERC20()', () => { describe('_transformERC20()', () => {
let inputToken: TestMintableERC20TokenContract; let inputToken: TestMintableERC20TokenContract;
let outputToken: TestMintableERC20TokenContract; let outputToken: TestMintableERC20TokenContract;
let mintTransformer: TestMintTokenERC20TransformerContract; let mintTransformer: TestMintTokenERC20TransformerContract;
let rlpNonce: string; let transformerNonce: number;
before(async () => { before(async () => {
inputToken = await TestMintableERC20TokenContract.deployFrom0xArtifactAsync( inputToken = await TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
@ -83,7 +115,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
env.txDefaults, env.txDefaults,
artifacts, artifacts,
); );
rlpNonce = await getRLPEncodedAccountNonceAsync(env.web3Wrapper, transformerDeployer); transformerNonce = await env.web3Wrapper.getAccountNonceAsync(transformerDeployer);
mintTransformer = await TestMintTokenERC20TransformerContract.deployFrom0xArtifactAsync( mintTransformer = await TestMintTokenERC20TransformerContract.deployFrom0xArtifactAsync(
artifacts.TestMintTokenERC20Transformer, artifacts.TestMintTokenERC20Transformer,
env.provider, env.provider,
@ -97,7 +129,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
}); });
interface Transformation { interface Transformation {
transformer: string; deploymentNonce: number;
data: string; data: string;
} }
@ -111,7 +143,6 @@ blockchainTests.resets('TransformERC20 feature', env => {
{ name: 'burnAmount', type: 'uint256' }, { name: 'burnAmount', type: 'uint256' },
{ name: 'mintAmount', type: 'uint256' }, { name: 'mintAmount', type: 'uint256' },
{ name: 'feeAmount', type: 'uint256' }, { name: 'feeAmount', type: 'uint256' },
{ name: 'deploymentNonce', type: 'bytes' },
], ],
}, },
]); ]);
@ -124,21 +155,21 @@ blockchainTests.resets('TransformERC20 feature', env => {
inputTokenBurnAmunt: Numberish; inputTokenBurnAmunt: Numberish;
outputTokenMintAmount: Numberish; outputTokenMintAmount: Numberish;
outputTokenFeeAmount: Numberish; outputTokenFeeAmount: Numberish;
rlpNonce: string; deploymentNonce: number;
}> = {}, }> = {},
): Transformation { ): Transformation {
const _opts = { const _opts = {
rlpNonce,
outputTokenAddress: outputToken.address, outputTokenAddress: outputToken.address,
inputTokenAddress: inputToken.address, inputTokenAddress: inputToken.address,
inputTokenBurnAmunt: ZERO_AMOUNT, inputTokenBurnAmunt: ZERO_AMOUNT,
outputTokenMintAmount: ZERO_AMOUNT, outputTokenMintAmount: ZERO_AMOUNT,
outputTokenFeeAmount: ZERO_AMOUNT, outputTokenFeeAmount: ZERO_AMOUNT,
transformer: mintTransformer.address, transformer: mintTransformer.address,
deploymentNonce: transformerNonce,
...opts, ...opts,
}; };
return { return {
transformer: _opts.transformer, deploymentNonce: _opts.deploymentNonce,
data: transformDataEncoder.encode([ data: transformDataEncoder.encode([
{ {
inputToken: _opts.inputTokenAddress, inputToken: _opts.inputTokenAddress,
@ -146,7 +177,6 @@ blockchainTests.resets('TransformERC20 feature', env => {
burnAmount: _opts.inputTokenBurnAmunt, burnAmount: _opts.inputTokenBurnAmunt,
mintAmount: _opts.outputTokenMintAmount, mintAmount: _opts.outputTokenMintAmount,
feeAmount: _opts.outputTokenFeeAmount, feeAmount: _opts.outputTokenFeeAmount,
deploymentNonce: _opts.rlpNonce,
}, },
]), ]),
}; };
@ -443,7 +473,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
); );
}); });
it('fails with third-party transformer', async () => { it('fails with invalid transformer nonce', async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18'); const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(2, '100e18'); const startingInputTokenBalance = getRandomInteger(2, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync(); await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
@ -452,32 +482,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
const minOutputTokenAmount = getRandomInteger(2, '1e18'); const minOutputTokenAmount = getRandomInteger(2, '1e18');
const callValue = getRandomInteger(1, '1e18'); const callValue = getRandomInteger(1, '1e18');
const callDataHash = hexUtils.random(); const callDataHash = hexUtils.random();
const transformations = [createMintTokenTransformation({ transformer: randomAddress() })]; const transformations = [createMintTokenTransformation({ deploymentNonce: 1337 })];
const tx = feature
._transformERC20(
callDataHash,
taker,
inputToken.address,
outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
transformations,
)
.awaitTransactionSuccessAsync({ value: callValue });
return expect(tx).to.revertWith(new ZeroExRevertErrors.TransformERC20.InvalidRLPNonceError(NULL_BYTES));
});
it('fails with incorrect transformer RLP nonce', async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(2, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(2, '1e18');
const callValue = getRandomInteger(1, '1e18');
const callDataHash = hexUtils.random();
const badRlpNonce = '0x00';
const transformations = [createMintTokenTransformation({ rlpNonce: badRlpNonce })];
const tx = feature const tx = feature
._transformERC20( ._transformERC20(
callDataHash, callDataHash,
@ -490,36 +495,12 @@ blockchainTests.resets('TransformERC20 feature', env => {
) )
.awaitTransactionSuccessAsync({ value: callValue }); .awaitTransactionSuccessAsync({ value: callValue });
return expect(tx).to.revertWith( return expect(tx).to.revertWith(
new ZeroExRevertErrors.TransformERC20.UnauthorizedTransformerError( new ZeroExRevertErrors.TransformERC20.TransformerFailedError(
transformations[0].transformer, undefined,
badRlpNonce, transformations[0].data,
constants.NULL_BYTES,
), ),
); );
}); });
it('fails with invalid transformer RLP nonce', async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(2, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(2, '1e18');
const callValue = getRandomInteger(1, '1e18');
const callDataHash = hexUtils.random();
const badRlpNonce = '0x010203040506';
const transformations = [createMintTokenTransformation({ rlpNonce: badRlpNonce })];
const tx = feature
._transformERC20(
callDataHash,
taker,
inputToken.address,
outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
transformations,
)
.awaitTransactionSuccessAsync({ value: callValue });
return expect(tx).to.revertWith(new ZeroExRevertErrors.TransformERC20.InvalidRLPNonceError(badRlpNonce));
});
}); });
}); });

View File

@ -23,6 +23,7 @@ blockchainTests.resets('Full migration', env => {
let zeroEx: ZeroExContract; let zeroEx: ZeroExContract;
let features: FullFeatures; let features: FullFeatures;
let migrator: TestFullMigrationContract; let migrator: TestFullMigrationContract;
const transformerDeployer = randomAddress();
before(async () => { before(async () => {
[owner] = await env.getAccountAddressesAsync(); [owner] = await env.getAccountAddressesAsync();
@ -34,7 +35,7 @@ blockchainTests.resets('Full migration', env => {
artifacts, artifacts,
env.txDefaults.from as string, env.txDefaults.from as string,
); );
const deployCall = migrator.deploy(owner, toFeatureAdddresses(features)); const deployCall = migrator.deploy(owner, toFeatureAdddresses(features), { transformerDeployer });
zeroEx = new ZeroExContract(await deployCall.callAsync(), env.provider, env.txDefaults); zeroEx = new ZeroExContract(await deployCall.callAsync(), env.provider, env.txDefaults);
await deployCall.awaitTransactionSuccessAsync(); await deployCall.awaitTransactionSuccessAsync();
}); });
@ -52,7 +53,9 @@ blockchainTests.resets('Full migration', env => {
it('Non-deployer cannot call deploy()', async () => { it('Non-deployer cannot call deploy()', async () => {
const notDeployer = randomAddress(); const notDeployer = randomAddress();
const tx = migrator.deploy(owner, toFeatureAdddresses(features)).callAsync({ from: notDeployer }); const tx = migrator
.deploy(owner, toFeatureAdddresses(features), { transformerDeployer })
.callAsync({ from: notDeployer });
return expect(tx).to.revertWith('FullMigration/INVALID_SENDER'); return expect(tx).to.revertWith('FullMigration/INVALID_SENDER');
}); });
@ -63,7 +66,13 @@ blockchainTests.resets('Full migration', env => {
}, },
TransformERC20: { TransformERC20: {
contractType: ITransformERC20Contract, contractType: ITransformERC20Contract,
fns: ['transformERC20', '_transformERC20', 'createTransformWallet', 'getTransformWallet'], fns: [
'transformERC20',
'_transformERC20',
'createTransformWallet',
'getTransformWallet',
'setTransformerDeployer',
],
}, },
}; };
@ -162,4 +171,16 @@ blockchainTests.resets('Full migration', env => {
return expect(allowanceTarget.authorized(zeroEx.address).callAsync()).to.become(true); return expect(allowanceTarget.authorized(zeroEx.address).callAsync()).to.become(true);
}); });
}); });
describe('TransformERC20', () => {
let feature: ITransformERC20Contract;
before(async () => {
feature = new ITransformERC20Contract(zeroEx.address, env.provider, env.txDefaults);
});
it('has the correct transformer deployer', async () => {
return expect(feature.getTransformerDeployer().callAsync()).to.become(transformerDeployer);
});
});
}); });

View File

@ -12,7 +12,6 @@ import { Order } from '@0x/types';
import { BigNumber, hexUtils, ZeroExRevertErrors } from '@0x/utils'; import { BigNumber, hexUtils, ZeroExRevertErrors } from '@0x/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { rlpEncodeNonce } from '../../src/nonce_utils';
import { import {
encodeFillQuoteTransformerData, encodeFillQuoteTransformerData,
FillQuoteTransformerData, FillQuoteTransformerData,
@ -29,7 +28,6 @@ import {
const { NULL_ADDRESS, NULL_BYTES, MAX_UINT256, ZERO_AMOUNT } = constants; const { NULL_ADDRESS, NULL_BYTES, MAX_UINT256, ZERO_AMOUNT } = constants;
blockchainTests.resets('FillQuoteTransformer', env => { blockchainTests.resets('FillQuoteTransformer', env => {
const deploymentNonce = _.random(0, 0xffffffff);
let maker: string; let maker: string;
let feeRecipient: string; let feeRecipient: string;
let exchange: TestFillQuoteTransformerExchangeContract; let exchange: TestFillQuoteTransformerExchangeContract;
@ -56,7 +54,6 @@ blockchainTests.resets('FillQuoteTransformer', env => {
env.txDefaults, env.txDefaults,
artifacts, artifacts,
exchange.address, exchange.address,
new BigNumber(deploymentNonce),
); );
host = await TestFillQuoteTransformerHostContract.deployFrom0xArtifactAsync( host = await TestFillQuoteTransformerHostContract.deployFrom0xArtifactAsync(
artifacts.TestFillQuoteTransformerHost, artifacts.TestFillQuoteTransformerHost,
@ -585,24 +582,6 @@ blockchainTests.resets('FillQuoteTransformer', env => {
makerAssetBalance: qfr.makerAssetBought, makerAssetBalance: qfr.makerAssetBought,
}); });
}); });
it('returns the RLP-encoded nonce', async () => {
const orders = _.times(1, () => createOrder());
const signatures = orders.map(() => encodeExchangeBehavior());
const qfr = getExpectedSellQuoteFillResults(orders);
const r = await host
.executeTransform(
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
encodeTransformData({
orders,
signatures,
}),
)
.callAsync({ value: qfr.protocolFeePaid });
expect(r).to.eq(rlpEncodeNonce(deploymentNonce));
});
}); });
describe('buy quotes', () => { describe('buy quotes', () => {
@ -881,25 +860,5 @@ blockchainTests.resets('FillQuoteTransformer', env => {
makerAssetBalance: qfr.makerAssetBought, makerAssetBalance: qfr.makerAssetBought,
}); });
}); });
it('returns the RLP-encoded nonce', async () => {
const orders = _.times(1, () => createOrder());
const signatures = orders.map(() => encodeExchangeBehavior());
const qfr = getExpectedBuyQuoteFillResults(orders);
const r = await host
.executeTransform(
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
encodeTransformData({
orders,
signatures,
side: FillQuoteTransformerSide.Buy,
fillAmount: qfr.makerAssetBought,
}),
)
.callAsync({ value: qfr.protocolFeePaid });
expect(r).to.eq(rlpEncodeNonce(deploymentNonce));
});
}); });
}); });

View File

@ -3,7 +3,6 @@ import { BigNumber, hexUtils } from '@0x/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { ETH_TOKEN_ADDRESS } from '../../src/constants'; import { ETH_TOKEN_ADDRESS } from '../../src/constants';
import { rlpEncodeNonce } from '../../src/nonce_utils';
import { encodePayTakerTransformerData } from '../../src/transformer_data_encoders'; import { encodePayTakerTransformerData } from '../../src/transformer_data_encoders';
import { artifacts } from '../artifacts'; import { artifacts } from '../artifacts';
import { PayTakerTransformerContract, TestMintableERC20TokenContract, TestTransformerHostContract } from '../wrappers'; import { PayTakerTransformerContract, TestMintableERC20TokenContract, TestTransformerHostContract } from '../wrappers';
@ -12,7 +11,6 @@ const { MAX_UINT256, ZERO_AMOUNT } = constants;
blockchainTests.resets('PayTakerTransformer', env => { blockchainTests.resets('PayTakerTransformer', env => {
const taker = randomAddress(); const taker = randomAddress();
const deploymentNonce = _.random(0, 0xffffffff);
let caller: string; let caller: string;
let token: TestMintableERC20TokenContract; let token: TestMintableERC20TokenContract;
let transformer: PayTakerTransformerContract; let transformer: PayTakerTransformerContract;
@ -31,7 +29,6 @@ blockchainTests.resets('PayTakerTransformer', env => {
env.provider, env.provider,
env.txDefaults, env.txDefaults,
artifacts, artifacts,
new BigNumber(deploymentNonce),
); );
host = await TestTransformerHostContract.deployFrom0xArtifactAsync( host = await TestTransformerHostContract.deployFrom0xArtifactAsync(
artifacts.TestTransformerHost, artifacts.TestTransformerHost,
@ -147,16 +144,4 @@ blockchainTests.resets('PayTakerTransformer', env => {
ethBalance: amounts[1].dividedToIntegerBy(2), ethBalance: amounts[1].dividedToIntegerBy(2),
}); });
}); });
it('returns the RLP-encoded nonce', async () => {
const amounts = _.times(2, () => getRandomInteger(1, '1e18'));
const data = encodePayTakerTransformerData({
amounts,
tokens: [token.address, ETH_TOKEN_ADDRESS],
});
await mintHostTokensAsync(amounts[0]);
await sendEtherAsync(host.address, amounts[1]);
const r = await host.rawExecuteTransform(transformer.address, hexUtils.random(), taker, data).callAsync();
expect(r).to.eq(rlpEncodeNonce(deploymentNonce));
});
}); });

View File

@ -1,13 +1,11 @@
import { blockchainTests, constants, expect, randomAddress } from '@0x/contracts-test-utils'; import { blockchainTests, constants, expect, randomAddress } from '@0x/contracts-test-utils';
import { BigNumber, ZeroExRevertErrors } from '@0x/utils'; import { ZeroExRevertErrors } from '@0x/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { rlpEncodeNonce } from '../../src/nonce_utils';
import { artifacts } from '../artifacts'; import { artifacts } from '../artifacts';
import { TestDelegateCallerContract, TestTransformerBaseContract } from '../wrappers'; import { TestDelegateCallerContract, TestTransformerBaseContract } from '../wrappers';
blockchainTests.resets('Transformer (base)', env => { blockchainTests.resets('Transformer (base)', env => {
const deploymentNonce = _.random(0, 0xffffffff);
let deployer: string; let deployer: string;
let delegateCaller: TestDelegateCallerContract; let delegateCaller: TestDelegateCallerContract;
let transformer: TestTransformerBaseContract; let transformer: TestTransformerBaseContract;
@ -28,17 +26,9 @@ blockchainTests.resets('Transformer (base)', env => {
from: deployer, from: deployer,
}, },
artifacts, 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()', () => { describe('die()', () => {
it('cannot be called by non-deployer', async () => { it('cannot be called by non-deployer', async () => {
const notDeployer = randomAddress(); const notDeployer = randomAddress();

View File

@ -3,7 +3,6 @@ import { BigNumber, ZeroExRevertErrors } from '@0x/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { ETH_TOKEN_ADDRESS } from '../../src/constants'; import { ETH_TOKEN_ADDRESS } from '../../src/constants';
import { rlpEncodeNonce } from '../../src/nonce_utils';
import { encodeWethTransformerData } from '../../src/transformer_data_encoders'; import { encodeWethTransformerData } from '../../src/transformer_data_encoders';
import { artifacts } from '../artifacts'; import { artifacts } from '../artifacts';
import { TestWethContract, TestWethTransformerHostContract, WethTransformerContract } from '../wrappers'; import { TestWethContract, TestWethTransformerHostContract, WethTransformerContract } from '../wrappers';
@ -11,7 +10,6 @@ import { TestWethContract, TestWethTransformerHostContract, WethTransformerContr
const { MAX_UINT256, ZERO_AMOUNT } = constants; const { MAX_UINT256, ZERO_AMOUNT } = constants;
blockchainTests.resets('WethTransformer', env => { blockchainTests.resets('WethTransformer', env => {
const deploymentNonce = _.random(0, 0xffffffff);
let weth: TestWethContract; let weth: TestWethContract;
let transformer: WethTransformerContract; let transformer: WethTransformerContract;
let host: TestWethTransformerHostContract; let host: TestWethTransformerHostContract;
@ -29,7 +27,6 @@ blockchainTests.resets('WethTransformer', env => {
env.txDefaults, env.txDefaults,
artifacts, artifacts,
weth.address, weth.address,
new BigNumber(deploymentNonce),
); );
host = await TestWethTransformerHostContract.deployFrom0xArtifactAsync( host = await TestWethTransformerHostContract.deployFrom0xArtifactAsync(
artifacts.TestWethTransformerHost, artifacts.TestWethTransformerHost,
@ -152,14 +149,4 @@ blockchainTests.resets('WethTransformer', env => {
wethBalance: amount.dividedToIntegerBy(2), wethBalance: amount.dividedToIntegerBy(2),
}); });
}); });
it('returns the RLP-encoded nonce', async () => {
const amount = getRandomInteger(1, '1e18');
const data = encodeWethTransformerData({
amount,
token: weth.address,
});
const r = await host.executeTransform(amount, transformer.address, data).callAsync({ value: amount });
expect(r).to.eq(rlpEncodeNonce(deploymentNonce));
});
}); });

View File

@ -67,14 +67,13 @@ export interface FullFeatures extends BootstrapFeatures {
} }
export interface FullMigrationOpts { export interface FullMigrationOpts {
transformDeployer: string; transformerDeployer: string;
} }
export async function deployFullFeaturesAsync( export async function deployFullFeaturesAsync(
provider: SupportedProvider, provider: SupportedProvider,
txDefaults: Partial<TxData>, txDefaults: Partial<TxData>,
features: Partial<FullFeatures> = {}, features: Partial<FullFeatures> = {},
opts: Partial<FullMigrationOpts> = {},
): Promise<FullFeatures> { ): Promise<FullFeatures> {
return { return {
...(await deployBootstrapFeaturesAsync(provider, txDefaults)), ...(await deployBootstrapFeaturesAsync(provider, txDefaults)),
@ -93,7 +92,6 @@ export async function deployFullFeaturesAsync(
provider, provider,
txDefaults, txDefaults,
artifacts, artifacts,
opts.transformDeployer || (txDefaults.from as string),
)), )),
}; };
} }
@ -105,7 +103,7 @@ export async function fullMigrateAsync(
features: Partial<FullFeatures> = {}, features: Partial<FullFeatures> = {},
opts: Partial<FullMigrationOpts> = {}, opts: Partial<FullMigrationOpts> = {},
): Promise<ZeroExContract> { ): Promise<ZeroExContract> {
const _features = await deployFullFeaturesAsync(provider, txDefaults, features, opts); const _features = await deployFullFeaturesAsync(provider, txDefaults, features);
const migrator = await FullMigrationContract.deployFrom0xArtifactAsync( const migrator = await FullMigrationContract.deployFrom0xArtifactAsync(
artifacts.FullMigration, artifacts.FullMigration,
provider, provider,
@ -113,7 +111,11 @@ export async function fullMigrateAsync(
artifacts, artifacts,
txDefaults.from as string, txDefaults.from as string,
); );
const deployCall = migrator.deploy(owner, toFeatureAdddresses(_features)); const _opts = {
transformerDeployer: txDefaults.from as string,
...opts,
};
const deployCall = migrator.deploy(owner, toFeatureAdddresses(_features), _opts);
const zeroEx = new ZeroExContract(await deployCall.callAsync(), provider, {}); const zeroEx = new ZeroExContract(await deployCall.callAsync(), provider, {});
await deployCall.awaitTransactionSuccessAsync(); await deployCall.awaitTransactionSuccessAsync();
return zeroEx; return zeroEx;