@0x/contracts-zero-ex
: Address audit feedback.
This commit is contained in:
parent
cb2cc05cac
commit
ebfa62637e
@ -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
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -35,6 +35,5 @@ interface IAllowanceTarget is
|
|||||||
bytes calldata callData
|
bytes calldata callData
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
payable
|
|
||||||
returns (bytes memory resultData);
|
returns (bytes memory resultData);
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
|
||||||
)))));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
);
|
);
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
)))));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -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));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -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();
|
||||||
|
@ -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));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user