@0x/contracts-zero-ex: Add RFQT taker enforcement to FQT.

`@0x/contracts-zero-ex`: Remove redundant event from `BridgeAdapter`.
`@0x/contracts-zero-ex`: Treat all calldata as signed if quote signer is not in `TransformERC20Feature`.

`@0x/contracts-zero-ex`: Update bridge adapters
This commit is contained in:
Lawrence Forman 2020-09-01 22:11:05 -04:00
parent 43810835d7
commit 889b58a914
23 changed files with 423 additions and 167 deletions

View File

@ -106,7 +106,7 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => {
orders: SignedOrder[];
}
async function generateSwapAsync(orderFields: Partial<Order> = {}): Promise<SwapInfo> {
async function generateSwapAsync(orderFields: Partial<Order> = {}, isRfqt: boolean = false): Promise<SwapInfo> {
const order = await signatureUtils.ecSignTypedDataOrderAsync(
env.provider,
{
@ -116,7 +116,7 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => {
salt: new BigNumber(hexUtils.random()),
feeRecipientAddress: NULL_ADDRESS,
senderAddress: NULL_ADDRESS,
takerAddress: flashWalletAddress,
takerAddress: isRfqt ? flashWalletAddress : NULL_ADDRESS,
makerAddress: maker,
makerAssetData: assetDataUtils.encodeERC20AssetData(outputToken.address),
takerAssetData: assetDataUtils.encodeERC20AssetData(inputToken.address),
@ -144,6 +144,7 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => {
fillAmount: order.takerAssetAmount,
maxOrderFillAmounts: [],
refundReceiver: hexUtils.leftPad(2, 20), // Send refund to sender.
rfqtTakerAddress: isRfqt ? taker : NULL_ADDRESS,
side: FillQuoteTransformerSide.Sell,
}),
},
@ -205,6 +206,7 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => {
async function createMetaTransactionAsync(
data: string,
value: BigNumber,
fee?: BigNumber | number,
): Promise<SignedExchangeProxyMetaTransaction> {
return signatureUtils.ecSignTypedDataExchangeProxyMetaTransactionAsync(
env.provider,
@ -218,7 +220,7 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => {
salt: new BigNumber(hexUtils.random()),
callData: data,
feeToken: feeToken.address,
feeAmount: getRandomPortion(TAKER_FEE_BALANCE),
feeAmount: fee !== undefined ? new BigNumber(fee) : getRandomPortion(TAKER_FEE_BALANCE),
domain: {
chainId: 1,
name: 'ZeroEx',
@ -230,6 +232,42 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => {
);
}
it('can call `transformERC20()` with signed calldata and no relayer fee', async () => {
const swap = await generateSwapAsync();
const callDataHash = hexUtils.hash(getSwapData(swap));
const signedSwapData = getSignedSwapData(swap);
const _protocolFee = protocolFee.times(GAS_PRICE).times(swap.orders.length + 1); // Pay a little more fee than needed.
const mtx = await createMetaTransactionAsync(signedSwapData, _protocolFee, 0);
const relayerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(relayer);
const receipt = await zeroEx
.executeMetaTransaction(mtx, mtx.signature)
.awaitTransactionSuccessAsync({ from: relayer, value: mtx.value, gasPrice: GAS_PRICE });
const relayerEthRefund = relayerEthBalanceBefore
.minus(await env.web3Wrapper.getBalanceInWeiAsync(relayer))
.minus(GAS_PRICE.times(receipt.gasUsed));
// Ensure the relayer got back the unused protocol fees.
expect(relayerEthRefund).to.bignumber.eq(protocolFee.times(GAS_PRICE));
// Ensure the relayer got paid no mtx fees.
expect(await feeToken.balanceOf(relayer).callAsync()).to.bignumber.eq(0);
// Ensure the taker got output tokens.
expect(await outputToken.balanceOf(taker).callAsync()).to.bignumber.eq(swap.minOutputTokenAmount);
// Ensure the maker got input tokens.
expect(await inputToken.balanceOf(maker).callAsync()).to.bignumber.eq(swap.inputTokenAmount);
// Check events.
verifyEventsFromLogs(
receipt.logs,
[
{
taker,
callDataHash,
sender: zeroEx.address,
data: NULL_BYTES,
},
],
'TransformerMetadata',
);
});
it('can call `transformERC20()` with signed calldata and a relayer fee', async () => {
const swap = await generateSwapAsync();
const callDataHash = hexUtils.hash(getSwapData(swap));
@ -301,4 +339,40 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => {
'TransformerMetadata',
);
});
it('`transformERC20()` can fill RFQT order if calldata is signed', async () => {
const swap = await generateSwapAsync({}, true);
const callDataHash = hexUtils.hash(getSwapData(swap));
const signedSwapData = getSignedSwapData(swap);
const _protocolFee = protocolFee.times(GAS_PRICE).times(swap.orders.length + 1); // Pay a little more fee than needed.
const mtx = await createMetaTransactionAsync(signedSwapData, _protocolFee, 0);
const relayerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(relayer);
const receipt = await zeroEx
.executeMetaTransaction(mtx, mtx.signature)
.awaitTransactionSuccessAsync({ from: relayer, value: mtx.value, gasPrice: GAS_PRICE });
const relayerEthRefund = relayerEthBalanceBefore
.minus(await env.web3Wrapper.getBalanceInWeiAsync(relayer))
.minus(GAS_PRICE.times(receipt.gasUsed));
// Ensure the relayer got back the unused protocol fees.
expect(relayerEthRefund).to.bignumber.eq(protocolFee.times(GAS_PRICE));
// Ensure the relayer got paid no mtx fees.
expect(await feeToken.balanceOf(relayer).callAsync()).to.bignumber.eq(0);
// Ensure the taker got output tokens.
expect(await outputToken.balanceOf(taker).callAsync()).to.bignumber.eq(swap.minOutputTokenAmount);
// Ensure the maker got input tokens.
expect(await inputToken.balanceOf(maker).callAsync()).to.bignumber.eq(swap.inputTokenAmount);
// Check events.
verifyEventsFromLogs(
receipt.logs,
[
{
taker,
callDataHash,
sender: zeroEx.address,
data: NULL_BYTES,
},
],
'TransformerMetadata',
);
});
});

View File

@ -30,10 +30,6 @@
"note": "Add `LogMetadataTransformer`",
"pr": 2657
},
{
"note": "Add `IUniswapV2Feature`",
"pr": 2657
},
{
"note": "Rename all feature contracts to have `Feature` suffix",
"pr": 2657
@ -41,6 +37,18 @@
{
"note": "Return `IZeroExContract` in `fullMigrateAsync()`",
"pr": 2657
},
{
"note": "Add taker address enforcement to RFQT orders in FQT",
"pr": 2692
},
{
"note": "All calldata is valid if quote signer is unset in `TransformERC20`",
"pr": 2692
},
{
"note": "Add updated Kyber and Mooniswap rollup to FQT",
"pr": 2692
}
]
},

View File

@ -386,13 +386,19 @@ contract TransformERC20Feature is
view
returns (bytes32 validCallDataHash)
{
address quoteSigner = getQuoteSigner();
if (quoteSigner == address(0)) {
// If no quote signer is configured, then all calldata hashes are
// valid.
return callDataHash;
}
if (signature.length == 0) {
return bytes32(0);
}
if (ISignatureValidatorFeature(address(this)).isValidHashSignature(
callDataHash,
getQuoteSigner(),
quoteSigner,
signature
)) {
return callDataHash;

View File

@ -27,7 +27,7 @@ import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
import "../errors/LibTransformERC20RichErrors.sol";
import "../vendor/v3/IExchange.sol";
import "../bridges/IBridgeAdapter.sol";
import "./bridges/IBridgeAdapter.sol";
import "./Transformer.sol";
import "./LibERC20Transformer.sol";
@ -50,7 +50,7 @@ contract FillQuoteTransformer is
/// @dev Transform data to ABI-encode and pass into `transform()`.
struct TransformData {
// Whether we aer performing a market sell or buy.
// Whether we are performing a market sell or buy.
Side side;
// The token being sold.
// This should be an actual token, not the ETH pseudo-token.
@ -77,6 +77,9 @@ contract FillQuoteTransformer is
// `address(1)`: Send to the taker.
// `address(2)`: Send to the sender (caller of `transformERC20()`).
address payable refundReceiver;
// Required taker address for RFQT orders.
// Null means any taker can fill it.
address rfqtTakerAddress;
}
/// @dev Results of a call to `_fillOrder()`.
@ -96,6 +99,7 @@ contract FillQuoteTransformer is
uint256 soldAmount;
uint256 protocolFee;
uint256 takerTokenBalanceRemaining;
bool isRfqtAllowed;
}
/// @dev Emitted when a trade is skipped due to a lack of funds
@ -178,9 +182,14 @@ contract FillQuoteTransformer is
// Approve the ERC20 proxy to spend `sellToken`.
data.sellToken.approveIfBelow(erc20Proxy, data.fillAmount);
// Fill the orders.
state.protocolFee = exchange.protocolFeeMultiplier().safeMul(tx.gasprice);
state.ethRemaining = address(this).balance;
// RFQT orders can only be filled if we have a valid calldata hash
// (calldata was signed), and the actual taker matches the RFQT taker (if set).
state.isRfqtAllowed = context.callDataHash != bytes32(0)
&& (data.rfqtTakerAddress == address(0) || context.taker == data.rfqtTakerAddress);
// Fill the orders.
for (uint256 i = 0; i < data.orders.length; ++i) {
// Check if we've hit our targets.
if (data.side == Side.Sell) {
@ -434,6 +443,11 @@ contract FillQuoteTransformer is
}
return results;
} else {
// If the order taker address is set to this contract's address then
// this is an RFQT order, and we will only fill it if allowed to.
if (order.takerAddress == address(this) && !state.isRfqtAllowed) {
return results; // Empty results.
}
// Emit an event if we do not have sufficient ETH to cover the protocol fee.
if (state.ethRemaining < state.protocolFee) {
emit ProtocolFeeUnfunded(state.ethRemaining, state.protocolFee);

View File

@ -28,6 +28,7 @@ interface IERC20Transformer {
/// @dev Context information to pass into `transform()` by `TransformERC20.transformERC20()`.
struct TransformContext {
// The hash of the `TransformERC20.transformERC20()` calldata.
// Will be null if the calldata is not signed.
bytes32 callDataHash;
// The caller of `TransformERC20.transformERC20()`.
address payable sender;

View File

@ -23,6 +23,7 @@ import "./mixins/MixinAdapterAddresses.sol";
import "./mixins/MixinBalancer.sol";
import "./mixins/MixinCurve.sol";
import "./mixins/MixinKyber.sol";
import "./mixins/MixinMooniswap.sol";
import "./mixins/MixinMStable.sol";
import "./mixins/MixinOasis.sol";
import "./mixins/MixinUniswap.sol";
@ -34,6 +35,7 @@ contract BridgeAdapter is
MixinBalancer,
MixinCurve,
MixinKyber,
MixinMooniswap,
MixinMStable,
MixinOasis,
MixinUniswap,
@ -44,6 +46,7 @@ contract BridgeAdapter is
address private immutable BALANCER_BRIDGE_ADDRESS;
address private immutable CURVE_BRIDGE_ADDRESS;
address private immutable KYBER_BRIDGE_ADDRESS;
address private immutable MOONISWAP_BRIDGE_ADDRESS;
address private immutable MSTABLE_BRIDGE_ADDRESS;
address private immutable OASIS_BRIDGE_ADDRESS;
address private immutable UNISWAP_BRIDGE_ADDRESS;
@ -57,8 +60,8 @@ contract BridgeAdapter is
/// @param from The bridge address, indicating the underlying source of the fill.
/// @param to The `to` address, currrently `address(this)`
event ERC20BridgeTransfer(
address inputToken,
address outputToken,
IERC20TokenV06 inputToken,
IERC20TokenV06 outputToken,
uint256 inputTokenAmount,
uint256 outputTokenAmount,
address from,
@ -70,6 +73,7 @@ contract BridgeAdapter is
MixinBalancer()
MixinCurve()
MixinKyber(addresses)
MixinMooniswap(addresses)
MixinMStable(addresses)
MixinOasis(addresses)
MixinUniswap(addresses)
@ -79,6 +83,7 @@ contract BridgeAdapter is
BALANCER_BRIDGE_ADDRESS = addresses.balancerBridge;
CURVE_BRIDGE_ADDRESS = addresses.curveBridge;
KYBER_BRIDGE_ADDRESS = addresses.kyberBridge;
MOONISWAP_BRIDGE_ADDRESS = addresses.mooniswapBridge;
MSTABLE_BRIDGE_ADDRESS = addresses.mStableBridge;
OASIS_BRIDGE_ADDRESS = addresses.oasisBridge;
UNISWAP_BRIDGE_ADDRESS = addresses.uniswapBridge;
@ -87,19 +92,19 @@ contract BridgeAdapter is
function trade(
bytes calldata makerAssetData,
address fromTokenAddress,
IERC20TokenV06 sellToken,
uint256 sellAmount
)
external
returns (uint256 boughtAmount)
{
(
address toTokenAddress,
IERC20TokenV06 buyToken,
address bridgeAddress,
bytes memory bridgeData
) = abi.decode(
makerAssetData[4:],
(address, address, bytes)
(IERC20TokenV06, address, bytes)
);
require(
bridgeAddress != address(this) && bridgeAddress != address(0),
@ -108,65 +113,71 @@ contract BridgeAdapter is
if (bridgeAddress == CURVE_BRIDGE_ADDRESS) {
boughtAmount = _tradeCurve(
toTokenAddress,
buyToken,
sellAmount,
bridgeData
);
} else if (bridgeAddress == UNISWAP_V2_BRIDGE_ADDRESS) {
boughtAmount = _tradeUniswapV2(
toTokenAddress,
buyToken,
sellAmount,
bridgeData
);
} else if (bridgeAddress == UNISWAP_BRIDGE_ADDRESS) {
boughtAmount = _tradeUniswap(
toTokenAddress,
buyToken,
sellAmount,
bridgeData
);
} else if (bridgeAddress == BALANCER_BRIDGE_ADDRESS) {
boughtAmount = _tradeBalancer(
toTokenAddress,
buyToken,
sellAmount,
bridgeData
);
} else if (bridgeAddress == KYBER_BRIDGE_ADDRESS) {
boughtAmount = _tradeKyber(
toTokenAddress,
buyToken,
sellAmount,
bridgeData
);
} else if (bridgeAddress == MOONISWAP_BRIDGE_ADDRESS) {
boughtAmount = _tradeMooniswap(
buyToken,
sellAmount,
bridgeData
);
} else if (bridgeAddress == MSTABLE_BRIDGE_ADDRESS) {
boughtAmount = _tradeMStable(
toTokenAddress,
buyToken,
sellAmount,
bridgeData
);
} else if (bridgeAddress == OASIS_BRIDGE_ADDRESS) {
boughtAmount = _tradeOasis(
toTokenAddress,
buyToken,
sellAmount,
bridgeData
);
} else {
boughtAmount = _tradeZeroExBridge(
bridgeAddress,
fromTokenAddress,
toTokenAddress,
sellToken,
buyToken,
sellAmount,
bridgeData
);
// Do not emit an event. The bridge contract should emit one itself.
return boughtAmount;
}
emit ERC20BridgeTransfer(
fromTokenAddress,
toTokenAddress,
sellToken,
buyToken,
sellAmount,
boughtAmount,
bridgeAddress,
address(this)
);
return boughtAmount;
}
}

View File

@ -26,6 +26,7 @@ contract MixinAdapterAddresses
address balancerBridge;
address curveBridge;
address kyberBridge;
address mooniswapBridge;
address mStableBridge;
address oasisBridge;
address uniswapBridge;
@ -36,6 +37,7 @@ contract MixinAdapterAddresses
address uniswapV2Router;
address uniswapExchangeFactory;
address mStable;
address mooniswapRegistry;
// Other
address weth;
}

View File

@ -32,9 +32,9 @@ interface IBalancerPool {
/// @return spotPriceAfter The new marginal spot price of the given
/// token pair for this pool.
function swapExactAmountIn(
address tokenIn,
IERC20TokenV06 tokenIn,
uint tokenAmountIn,
address tokenOut,
IERC20TokenV06 tokenOut,
uint minAmountOut,
uint maxPrice
) external returns (uint tokenAmountOut, uint spotPriceAfter);
@ -45,7 +45,7 @@ contract MixinBalancer {
using LibERC20TokenV06 for IERC20TokenV06;
function _tradeBalancer(
address toTokenAddress,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
@ -53,19 +53,19 @@ contract MixinBalancer {
returns (uint256 boughtAmount)
{
// Decode the bridge data.
(address fromTokenAddress, address poolAddress) = abi.decode(
(IERC20TokenV06 sellToken, IBalancerPool pool) = abi.decode(
bridgeData,
(address, address)
(IERC20TokenV06, IBalancerPool)
);
IERC20TokenV06(fromTokenAddress).approveIfBelow(
poolAddress,
sellToken.approveIfBelow(
address(pool),
sellAmount
);
// Sell all of this contract's `fromTokenAddress` token balance.
(boughtAmount,) = IBalancerPool(poolAddress).swapExactAmountIn(
fromTokenAddress, // tokenIn
// Sell all of this contract's `sellToken` token balance.
(boughtAmount,) = pool.swapExactAmountIn(
sellToken, // tokenIn
sellAmount, // tokenAmountIn
toTokenAddress, // tokenOut
buyToken, // tokenOut
1, // minAmountOut
uint256(-1) // maxPrice
);

View File

@ -34,13 +34,13 @@ contract MixinCurve {
struct CurveBridgeData {
address curveAddress;
bytes4 exchangeFunctionSelector;
address fromTokenAddress;
IERC20TokenV06 sellToken;
int128 fromCoinIdx;
int128 toCoinIdx;
}
function _tradeCurve(
address toTokenAddress,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
@ -49,8 +49,8 @@ contract MixinCurve {
{
// Decode the bridge data to get the Curve metadata.
CurveBridgeData memory data = abi.decode(bridgeData, (CurveBridgeData));
IERC20TokenV06(data.fromTokenAddress).approveIfBelow(data.curveAddress, sellAmount);
uint256 beforeBalance = IERC20TokenV06(toTokenAddress).balanceOf(address(this));
data.sellToken.approveIfBelow(data.curveAddress, sellAmount);
uint256 beforeBalance = buyToken.balanceOf(address(this));
(bool success, bytes memory resultData) =
data.curveAddress.call(abi.encodeWithSelector(
data.exchangeFunctionSelector,
@ -64,7 +64,6 @@ contract MixinCurve {
if (!success) {
resultData.rrevert();
}
return IERC20TokenV06(toTokenAddress).balanceOf(address(this)).safeSub(beforeBalance);
return buyToken.balanceOf(address(this)).safeSub(beforeBalance);
}
}

View File

@ -26,24 +26,27 @@ import "./MixinAdapterAddresses.sol";
interface IKyberNetworkProxy {
/// @dev Sells `sellTokenAddress` tokens for `buyTokenAddress` tokens.
/// @param sellTokenAddress Token to sell.
/// @dev Sells `sellTokenAddress` tokens for `buyTokenAddress` tokens
/// using a hint for the reserve.
/// @param sellToken Token to sell.
/// @param sellAmount Amount of tokens to sell.
/// @param buyTokenAddress Token to buy.
/// @param buyToken Token to buy.
/// @param recipientAddress Address to send bought tokens to.
/// @param maxBuyTokenAmount A limit on the amount of tokens to buy.
/// @param minConversionRate The minimal conversion rate. If actual rate
/// is lower, trade is canceled.
/// @param walletId The wallet ID to send part of the fees
/// @param hint The hint for the selective inclusion (or exclusion) of reserves
/// @return boughtAmount Amount of tokens bought.
function trade(
address sellTokenAddress,
function tradeWithHint(
IERC20TokenV06 sellToken,
uint256 sellAmount,
address buyTokenAddress,
IERC20TokenV06 buyToken,
address payable recipientAddress,
uint256 maxBuyTokenAmount,
uint256 minConversionRate,
address walletId
address payable walletId,
bytes calldata hint
)
external
payable
@ -53,7 +56,6 @@ interface IKyberNetworkProxy {
contract MixinKyber is
MixinAdapterAddresses
{
using LibERC20TokenV06 for IERC20TokenV06;
/// @dev Address indicating the trade is using ETH
@ -71,41 +73,39 @@ contract MixinKyber is
}
function _tradeKyber(
address toTokenAddress,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
// Decode the bridge data to get the `fromTokenAddress`.
address fromTokenAddress = abi.decode(bridgeData, (address));
uint256 payableAmount;
(IERC20TokenV06 sellToken, bytes memory hint) =
abi.decode(bridgeData, (IERC20TokenV06, bytes));
if (fromTokenAddress != address(WETH)) {
uint256 payableAmount = 0;
if (sellToken != WETH) {
// If the input token is not WETH, grant an allowance to the exchange
// to spend them.
IERC20TokenV06(fromTokenAddress).approveIfBelow(
sellToken.approveIfBelow(
address(KYBER_NETWORK_PROXY),
sellAmount
);
} else {
// If the input token is WETH, unwrap it and attach it to the call.
fromTokenAddress = KYBER_ETH_ADDRESS;
payableAmount = sellAmount;
WETH.withdraw(payableAmount);
}
bool isToTokenWeth = toTokenAddress == address(WETH);
// Try to sell all of this contract's input token balance through
// `KyberNetworkProxy.trade()`.
boughtAmount = KYBER_NETWORK_PROXY.trade{ value: payableAmount }(
boughtAmount = KYBER_NETWORK_PROXY.tradeWithHint{ value: payableAmount }(
// Input token.
fromTokenAddress,
sellToken == WETH ? IERC20TokenV06(KYBER_ETH_ADDRESS) : sellToken,
// Sell amount.
sellAmount,
// Output token.
isToTokenWeth ? KYBER_ETH_ADDRESS : toTokenAddress,
buyToken == WETH ? IERC20TokenV06(KYBER_ETH_ADDRESS) : buyToken,
// Transfer to this contract
address(uint160(address(this))),
// Buy as much as possible.
@ -113,9 +113,11 @@ contract MixinKyber is
// Lowest minimum conversion rate
1,
// No affiliate address.
address(0)
address(0),
hint
);
if (isToTokenWeth) {
// If receving ETH, wrap it to WETH.
if (buyToken == WETH) {
WETH.deposit{ value: boughtAmount }();
}
return boughtAmount;

View File

@ -27,19 +27,18 @@ import "./MixinAdapterAddresses.sol";
interface IMStable {
function swap(
address _input,
address _output,
uint256 _quantity,
address _recipient
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
address recipient
)
external
returns (uint256 output);
returns (uint256 boughtAmount);
}
contract MixinMStable is
MixinAdapterAddresses
{
using LibERC20TokenV06 for IERC20TokenV06;
/// @dev Mainnet address of the mStable mUSD contract.
@ -52,21 +51,21 @@ contract MixinMStable is
}
function _tradeMStable(
address toTokenAddress,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
// Decode the bridge data to get the `fromTokenAddress`.
(address fromTokenAddress) = abi.decode(bridgeData, (address));
// Grant an allowance to the exchange to spend `fromTokenAddress` token.
IERC20TokenV06(fromTokenAddress).approveIfBelow(address(MSTABLE), sellAmount);
// Decode the bridge data to get the `sellToken`.
(IERC20TokenV06 sellToken) = abi.decode(bridgeData, (IERC20TokenV06));
// Grant an allowance to the exchange to spend `sellToken` token.
sellToken.approveIfBelow(address(MSTABLE), sellAmount);
boughtAmount = MSTABLE.swap(
fromTokenAddress,
toTokenAddress,
sellToken,
buyToken,
sellAmount,
address(this)
);

View File

@ -0,0 +1,107 @@
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
import "./MixinAdapterAddresses.sol";
/// @dev Moooniswap pool interface.
interface IMooniswapPool {
function swap(
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
uint256 minBoughtAmount,
address referrer
)
external
payable
returns (uint256 boughtAmount);
}
/// @dev Moooniswap registry interface.
interface IMooniswapRegistry {
function pools(
IERC20TokenV06 token1,
IERC20TokenV06 token2
)
external
view
returns (IMooniswapPool);
}
/// @dev BridgeAdapter mixin for mooniswap.
contract MixinMooniswap is
MixinAdapterAddresses
{
using LibERC20TokenV06 for IERC20TokenV06;
using LibERC20TokenV06 for IEtherTokenV06;
/// @dev Mooniswap registry contract.
IMooniswapRegistry private immutable REGISTRY;
/// @dev WETH token.
IEtherTokenV06 private immutable WETH;
constructor(AdapterAddresses memory addresses)
public
{
REGISTRY = IMooniswapRegistry(addresses.mooniswapRegistry);
WETH = IEtherTokenV06(addresses.weth);
}
function _tradeMooniswap(
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
IERC20TokenV06 sellToken = abi.decode(bridgeData, (IERC20TokenV06));
IMooniswapPool pool = REGISTRY.pools(sellToken, buyToken);
// Convert WETH to ETH.
uint256 ethValue = 0;
if (sellToken == WETH) {
WETH.withdraw(sellAmount);
ethValue = sellAmount;
} else {
// Grant the pool an allowance.
sellToken.approveIfBelow(
address(pool),
sellAmount
);
}
boughtAmount = pool.swap{value: ethValue}(
sellToken == WETH ? IERC20TokenV06(0) : sellToken,
buyToken == WETH ? IERC20TokenV06(0) : buyToken,
sellAmount,
1,
address(0)
);
}
}

View File

@ -25,26 +25,25 @@ import "./MixinAdapterAddresses.sol";
interface IOasis {
/// @dev Sell `sellAmount` of `fromToken` token and receive `toToken` token.
/// @param fromToken The token being sold.
/// @param sellAmount The amount of `fromToken` token being sold.
/// @param toToken The token being bought.
/// @param minFillAmount Minimum amount of `toToken` token to buy.
/// @return fillAmount Amount of `toToken` bought.
/// @dev Sell `sellAmount` of `sellToken` token and receive `buyToken` token.
/// @param sellToken The token being sold.
/// @param sellAmount The amount of `sellToken` token being sold.
/// @param buyToken The token being bought.
/// @param minBoughtAmount Minimum amount of `buyToken` token to buy.
/// @return boughtAmount Amount of `buyToken` bought.
function sellAllAmount(
address fromToken,
IERC20TokenV06 sellToken,
uint256 sellAmount,
address toToken,
uint256 minFillAmount
IERC20TokenV06 buyToken,
uint256 minBoughtAmount
)
external
returns (uint256 fillAmount);
returns (uint256 boughtAmount);
}
contract MixinOasis is
MixinAdapterAddresses
{
using LibERC20TokenV06 for IERC20TokenV06;
/// @dev Mainnet address of the Oasis `MatchingMarket` contract.
@ -57,25 +56,25 @@ contract MixinOasis is
}
function _tradeOasis(
address toTokenAddress,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
// Decode the bridge data to get the `fromTokenAddress`.
(address fromTokenAddress) = abi.decode(bridgeData, (address));
// Grant an allowance to the exchange to spend `fromTokenAddress` token.
IERC20TokenV06(fromTokenAddress).approveIfBelow(
// Decode the bridge data to get the `sellToken`.
(IERC20TokenV06 sellToken) = abi.decode(bridgeData, (IERC20TokenV06));
// Grant an allowance to the exchange to spend `sellToken` token.
sellToken.approveIfBelow(
address(OASIS),
sellAmount
);
// Try to sell all of this contract's `fromTokenAddress` token balance.
// Try to sell all of this contract's `sellToken` token balance.
boughtAmount = OASIS.sellAllAmount(
fromTokenAddress,
sellToken,
sellAmount,
toTokenAddress,
buyToken,
// min fill amount
1
);

View File

@ -27,11 +27,11 @@ import "./MixinAdapterAddresses.sol";
interface IUniswapExchangeFactory {
/// @dev Get the exchange for a token.
/// @param tokenAddress The address of the token contract.
function getExchange(address tokenAddress)
/// @param token The token contract.
function getExchange(IERC20TokenV06 token)
external
view
returns (address);
returns (IUniswapExchange exchange);
}
interface IUniswapExchange {
@ -71,7 +71,7 @@ interface IUniswapExchange {
/// @param minEthBought The minimum amount of intermediate ETH to buy.
/// @param deadline Time when this order expires.
/// @param recipient Who to transfer the tokens to.
/// @param toTokenAddress The token being bought.
/// @param buyToken The token being bought.
/// @return tokensBought Amount of tokens bought.
function tokenToTokenTransferInput(
uint256 tokensSold,
@ -79,7 +79,7 @@ interface IUniswapExchange {
uint256 minEthBought,
uint256 deadline,
address recipient,
address toTokenAddress
IERC20TokenV06 buyToken
)
external
returns (uint256 tokensBought);
@ -89,14 +89,14 @@ interface IUniswapExchange {
/// @param minTokensBought The minimum number of tokens to buy.
/// @param minEthBought The minimum amount of intermediate ETH to buy.
/// @param deadline Time when this order expires.
/// @param toTokenAddress The token being bought.
/// @param buyToken The token being bought.
/// @return tokensBought Amount of tokens bought.
function tokenToTokenSwapInput(
uint256 tokensSold,
uint256 minTokensBought,
uint256 minEthBought,
uint256 deadline,
address toTokenAddress
IERC20TokenV06 buyToken
)
external
returns (uint256 tokensBought);
@ -105,7 +105,6 @@ interface IUniswapExchange {
contract MixinUniswap is
MixinAdapterAddresses
{
using LibERC20TokenV06 for IERC20TokenV06;
/// @dev Mainnet address of the WETH contract.
@ -121,27 +120,27 @@ contract MixinUniswap is
}
function _tradeUniswap(
address toTokenAddress,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
// Decode the bridge data to get the `fromTokenAddress`.
(address fromTokenAddress) = abi.decode(bridgeData, (address));
// Decode the bridge data to get the `sellToken`.
(IERC20TokenV06 sellToken) = abi.decode(bridgeData, (IERC20TokenV06));
// Get the exchange for the token pair.
IUniswapExchange exchange = _getUniswapExchangeForTokenPair(
fromTokenAddress,
toTokenAddress
sellToken,
buyToken
);
// Convert from WETH to a token.
if (fromTokenAddress == address(WETH)) {
if (sellToken == WETH) {
// Unwrap the WETH.
WETH.withdraw(sellAmount);
// Buy as much of `toTokenAddress` token with ETH as possible
// Buy as much of `buyToken` token with ETH as possible
boughtAmount = exchange.ethToTokenTransferInput{ value: sellAmount }(
// Minimum buy amount.
1,
@ -152,13 +151,13 @@ contract MixinUniswap is
);
// Convert from a token to WETH.
} else if (toTokenAddress == address(WETH)) {
} else if (buyToken == WETH) {
// Grant the exchange an allowance.
IERC20TokenV06(fromTokenAddress).approveIfBelow(
sellToken.approveIfBelow(
address(exchange),
sellAmount
);
// Buy as much ETH with `fromTokenAddress` token as possible.
// Buy as much ETH with `sellToken` token as possible.
boughtAmount = exchange.tokenToEthSwapInput(
// Sell all tokens we hold.
sellAmount,
@ -172,11 +171,11 @@ contract MixinUniswap is
// Convert from one token to another.
} else {
// Grant the exchange an allowance.
IERC20TokenV06(fromTokenAddress).approveIfBelow(
sellToken.approveIfBelow(
address(exchange),
sellAmount
);
// Buy as much `toTokenAddress` token with `fromTokenAddress` token
// Buy as much `buyToken` token with `sellToken` token
boughtAmount = exchange.tokenToTokenSwapInput(
// Sell all tokens we hold.
sellAmount,
@ -186,8 +185,8 @@ contract MixinUniswap is
1,
// Expires after this block.
block.timestamp,
// Convert to `toTokenAddress`.
toTokenAddress
// Convert to `buyToken`.
buyToken
);
}
@ -197,24 +196,21 @@ contract MixinUniswap is
/// @dev Retrieves the uniswap exchange for a given token pair.
/// In the case of a WETH-token exchange, this will be the non-WETH token.
/// In th ecase of a token-token exchange, this will be the first token.
/// @param fromTokenAddress The address of the token we are converting from.
/// @param toTokenAddress The address of the token we are converting to.
/// @param sellToken The address of the token we are converting from.
/// @param buyToken The address of the token we are converting to.
/// @return exchange The uniswap exchange.
function _getUniswapExchangeForTokenPair(
address fromTokenAddress,
address toTokenAddress
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken
)
private
view
returns (IUniswapExchange exchange)
{
address exchangeTokenAddress = fromTokenAddress;
// Whichever isn't WETH is the exchange token.
if (fromTokenAddress == address(WETH)) {
exchangeTokenAddress = toTokenAddress;
}
exchange = IUniswapExchange(UNISWAP_EXCHANGE_FACTORY.getExchange(exchangeTokenAddress));
exchange = sellToken == WETH
? UNISWAP_EXCHANGE_FACTORY.getExchange(buyToken)
: UNISWAP_EXCHANGE_FACTORY.getExchange(sellToken);
require(address(exchange) != address(0), "NO_UNISWAP_EXCHANGE_FOR_TOKEN");
return exchange;
}
}

View File

@ -50,7 +50,6 @@ interface IUniswapV2Router02 {
contract MixinUniswapV2 is
MixinAdapterAddresses
{
using LibERC20TokenV06 for IERC20TokenV06;
/// @dev Mainnet address of the `UniswapV2Router02` contract.
@ -63,21 +62,23 @@ contract MixinUniswapV2 is
}
function _tradeUniswapV2(
address toTokenAddress,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256)
returns (uint256 boughtAmount)
{
// Decode the bridge data to get the `fromTokenAddress`.
// solhint-disable indent
address[] memory path = abi.decode(bridgeData, (address[]));
// solhint-enable indent
require(path.length >= 2, "UniswapV2Bridge/PATH_LENGTH_MUST_BE_AT_LEAST_TWO");
require(path[path.length - 1] == toTokenAddress, "UniswapV2Bridge/LAST_ELEMENT_OF_PATH_MUST_MATCH_OUTPUT_TOKEN");
// Grant the Uniswap router an allowance.
require(
path[path.length - 1] == address(buyToken),
"UniswapV2Bridge/LAST_ELEMENT_OF_PATH_MUST_MATCH_OUTPUT_TOKEN"
);
// Grant the Uniswap router an allowance to sell the first token.
IERC20TokenV06(path[0]).approveIfBelow(
address(UNISWAP_V2_ROUTER),
sellAmount
@ -88,7 +89,7 @@ contract MixinUniswapV2 is
sellAmount,
// Minimum buy amount.
1,
// Convert `fromTokenAddress` to `toTokenAddress`.
// Convert to `buyToken` along this path.
path,
// Recipient is `this`.
address(this),

View File

@ -24,15 +24,15 @@ import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
interface IERC20Bridge {
/// @dev Transfers `amount` of the ERC20 `tokenAddress` from `from` to `to`.
/// @param tokenAddress The address of the ERC20 token to transfer.
/// @dev Transfers `amount` of the ERC20 `buyToken` from `from` to `to`.
/// @param buyToken The address of the ERC20 token to transfer.
/// @param from Address to transfer asset from.
/// @param to Address to transfer asset to.
/// @param amount Amount of asset to transfer.
/// @param bridgeData Arbitrary asset data needed by the bridge contract.
/// @return success The magic bytes `0xdc1600f3` if successful.
function bridgeTransferFrom(
address tokenAddress,
IERC20TokenV06 buyToken,
address from,
address to,
uint256 amount,
@ -49,28 +49,27 @@ contract MixinZeroExBridge {
function _tradeZeroExBridge(
address bridgeAddress,
address fromTokenAddress,
address toTokenAddress,
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
uint256 balanceBefore = IERC20TokenV06(toTokenAddress).balanceOf(address(this));
uint256 balanceBefore = buyToken.balanceOf(address(this));
// Trade the good old fashioned way
IERC20TokenV06(fromTokenAddress).compatTransfer(
sellToken.compatTransfer(
bridgeAddress,
sellAmount
);
IERC20Bridge(bridgeAddress).bridgeTransferFrom(
toTokenAddress,
bridgeAddress,
buyToken,
address(bridgeAddress),
address(this),
1, // amount to transfer back from the bridge
bridgeData
);
boughtAmount = IERC20TokenV06(toTokenAddress).balanceOf(address(this)).safeSub(balanceBefore);
boughtAmount = buyToken.balanceOf(address(this)).safeSub(balanceBefore);
}
}

View File

@ -41,7 +41,7 @@
"config": {
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,SignatureValidatorFeature,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IMetaTransactionsFeature|IOwnableFeature|ISignatureValidatorFeature|ISimpleFunctionRegistryFeature|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LogMetadataTransformer|MetaTransactionsFeature|MixinAdapterAddresses|MixinBalancer|MixinCurve|MixinKyber|MixinMStable|MixinOasis|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|OwnableFeature|PayTakerTransformer|SignatureValidatorFeature|SimpleFunctionRegistryFeature|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json"
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IMetaTransactionsFeature|IOwnableFeature|ISignatureValidatorFeature|ISimpleFunctionRegistryFeature|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LogMetadataTransformer|MetaTransactionsFeature|MixinAdapterAddresses|MixinBalancer|MixinCurve|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|OwnableFeature|PayTakerTransformer|SignatureValidatorFeature|SimpleFunctionRegistryFeature|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json"
},
"repository": {
"type": "git",

View File

@ -60,6 +60,7 @@ import * as MixinAdapterAddresses from '../test/generated-artifacts/MixinAdapter
import * as MixinBalancer from '../test/generated-artifacts/MixinBalancer.json';
import * as MixinCurve from '../test/generated-artifacts/MixinCurve.json';
import * as MixinKyber from '../test/generated-artifacts/MixinKyber.json';
import * as MixinMooniswap from '../test/generated-artifacts/MixinMooniswap.json';
import * as MixinMStable from '../test/generated-artifacts/MixinMStable.json';
import * as MixinOasis from '../test/generated-artifacts/MixinOasis.json';
import * as MixinUniswap from '../test/generated-artifacts/MixinUniswap.json';
@ -100,17 +101,6 @@ import * as ZeroEx from '../test/generated-artifacts/ZeroEx.json';
export const artifacts = {
IZeroEx: IZeroEx as ContractArtifact,
ZeroEx: ZeroEx as ContractArtifact,
BridgeAdapter: BridgeAdapter as ContractArtifact,
IBridgeAdapter: IBridgeAdapter as ContractArtifact,
MixinAdapterAddresses: MixinAdapterAddresses as ContractArtifact,
MixinBalancer: MixinBalancer as ContractArtifact,
MixinCurve: MixinCurve as ContractArtifact,
MixinKyber: MixinKyber as ContractArtifact,
MixinMStable: MixinMStable as ContractArtifact,
MixinOasis: MixinOasis as ContractArtifact,
MixinUniswap: MixinUniswap as ContractArtifact,
MixinUniswapV2: MixinUniswapV2 as ContractArtifact,
MixinZeroExBridge: MixinZeroExBridge as ContractArtifact,
LibCommonRichErrors: LibCommonRichErrors as ContractArtifact,
LibMetaTransactionsRichErrors: LibMetaTransactionsRichErrors as ContractArtifact,
LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact,
@ -164,6 +154,18 @@ export const artifacts = {
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
Transformer: Transformer as ContractArtifact,
WethTransformer: WethTransformer as ContractArtifact,
BridgeAdapter: BridgeAdapter as ContractArtifact,
IBridgeAdapter: IBridgeAdapter as ContractArtifact,
MixinAdapterAddresses: MixinAdapterAddresses as ContractArtifact,
MixinBalancer: MixinBalancer as ContractArtifact,
MixinCurve: MixinCurve as ContractArtifact,
MixinKyber: MixinKyber as ContractArtifact,
MixinMStable: MixinMStable as ContractArtifact,
MixinMooniswap: MixinMooniswap as ContractArtifact,
MixinOasis: MixinOasis as ContractArtifact,
MixinUniswap: MixinUniswap as ContractArtifact,
MixinUniswapV2: MixinUniswapV2 as ContractArtifact,
MixinZeroExBridge: MixinZeroExBridge as ContractArtifact,
IERC20Bridge: IERC20Bridge as ContractArtifact,
IExchange: IExchange as ContractArtifact,
IGasToken: IGasToken as ContractArtifact,

View File

@ -28,11 +28,11 @@ import {
TransformERC20FeatureEvents,
} from '../wrappers';
const { NULL_BYTES, NULL_BYTES32 } = constants;
const { NULL_ADDRESS, NULL_BYTES, NULL_BYTES32 } = constants;
type MintTokenTransformerEvent = DecodedLogEntry<TestMintTokenERC20TransformerMintTransformEventArgs>;
blockchainTests.resets('TransformERC20 feature', env => {
blockchainTests.resets.only('TransformERC20 feature', env => {
const callDataSignerKey = hexUtils.random();
const callDataSigner = ethjs.bufferToHex(ethjs.privateToAddress(ethjs.toBuffer(callDataSignerKey)));
let owner: string;
@ -687,6 +687,37 @@ blockchainTests.resets('TransformERC20 feature', env => {
expect(actualCallDataHash).to.eq(hexUtils.hash(callData));
});
it('passes the calldata hash to transformer when no quote signer configured', async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(0, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(1, '1e18');
const outputTokenMintAmount = minOutputTokenAmount;
const callValue = getRandomInteger(1, '1e18');
const transformation = createMintTokenTransformation({
outputTokenMintAmount,
inputTokenBurnAmunt: inputTokenAmount,
});
const bakedCall = feature.transformERC20(
inputToken.address,
outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
[transformation],
);
const callData = bakedCall.getABIEncodedTransactionData();
await feature.setQuoteSigner(NULL_ADDRESS).awaitTransactionSuccessAsync({ from: owner });
const receipt = await bakedCall.awaitTransactionSuccessAsync({
from: taker,
value: callValue,
data: callData,
});
const { callDataHash: actualCallDataHash } = (receipt.logs[0] as MintTokenTransformerEvent).args;
expect(actualCallDataHash).to.eq(hexUtils.hash(callData));
});
it('passes empty calldata hash to transformer with improperly signed calldata', async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(0, '100e18');

View File

@ -62,6 +62,7 @@ blockchainTests.resets('FillQuoteTransformer', env => {
balancerBridge: NULL_ADDRESS,
curveBridge: NULL_ADDRESS,
kyberBridge: NULL_ADDRESS,
mooniswapBridge: NULL_ADDRESS,
mStableBridge: NULL_ADDRESS,
oasisBridge: NULL_ADDRESS,
uniswapBridge: NULL_ADDRESS,
@ -71,6 +72,7 @@ blockchainTests.resets('FillQuoteTransformer', env => {
uniswapV2Router: NULL_ADDRESS,
uniswapExchangeFactory: NULL_ADDRESS,
mStable: NULL_ADDRESS,
mooniswapRegistry: NULL_ADDRESS,
weth: NULL_ADDRESS,
},
);
@ -273,6 +275,7 @@ blockchainTests.resets('FillQuoteTransformer', env => {
maxOrderFillAmounts: [],
fillAmount: MAX_UINT256,
refundReceiver: NULL_ADDRESS,
rfqtTakerAddress: NULL_ADDRESS,
...fields,
});
}

View File

@ -59,6 +59,7 @@ export * from '../test/generated-wrappers/mixin_balancer';
export * from '../test/generated-wrappers/mixin_curve';
export * from '../test/generated-wrappers/mixin_kyber';
export * from '../test/generated-wrappers/mixin_m_stable';
export * from '../test/generated-wrappers/mixin_mooniswap';
export * from '../test/generated-wrappers/mixin_oasis';
export * from '../test/generated-wrappers/mixin_uniswap';
export * from '../test/generated-wrappers/mixin_uniswap_v2';

View File

@ -82,6 +82,7 @@
"test/generated-artifacts/MixinCurve.json",
"test/generated-artifacts/MixinKyber.json",
"test/generated-artifacts/MixinMStable.json",
"test/generated-artifacts/MixinMooniswap.json",
"test/generated-artifacts/MixinOasis.json",
"test/generated-artifacts/MixinUniswap.json",
"test/generated-artifacts/MixinUniswapV2.json",