@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:
parent
43810835d7
commit
889b58a914
@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -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
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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
|
||||
);
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
@ -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)
|
||||
);
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
@ -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
|
||||
);
|
@ -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;
|
||||
}
|
||||
}
|
@ -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),
|
@ -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);
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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');
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user