@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[];
|
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(
|
const order = await signatureUtils.ecSignTypedDataOrderAsync(
|
||||||
env.provider,
|
env.provider,
|
||||||
{
|
{
|
||||||
@ -116,7 +116,7 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => {
|
|||||||
salt: new BigNumber(hexUtils.random()),
|
salt: new BigNumber(hexUtils.random()),
|
||||||
feeRecipientAddress: NULL_ADDRESS,
|
feeRecipientAddress: NULL_ADDRESS,
|
||||||
senderAddress: NULL_ADDRESS,
|
senderAddress: NULL_ADDRESS,
|
||||||
takerAddress: flashWalletAddress,
|
takerAddress: isRfqt ? flashWalletAddress : NULL_ADDRESS,
|
||||||
makerAddress: maker,
|
makerAddress: maker,
|
||||||
makerAssetData: assetDataUtils.encodeERC20AssetData(outputToken.address),
|
makerAssetData: assetDataUtils.encodeERC20AssetData(outputToken.address),
|
||||||
takerAssetData: assetDataUtils.encodeERC20AssetData(inputToken.address),
|
takerAssetData: assetDataUtils.encodeERC20AssetData(inputToken.address),
|
||||||
@ -144,6 +144,7 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => {
|
|||||||
fillAmount: order.takerAssetAmount,
|
fillAmount: order.takerAssetAmount,
|
||||||
maxOrderFillAmounts: [],
|
maxOrderFillAmounts: [],
|
||||||
refundReceiver: hexUtils.leftPad(2, 20), // Send refund to sender.
|
refundReceiver: hexUtils.leftPad(2, 20), // Send refund to sender.
|
||||||
|
rfqtTakerAddress: isRfqt ? taker : NULL_ADDRESS,
|
||||||
side: FillQuoteTransformerSide.Sell,
|
side: FillQuoteTransformerSide.Sell,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -205,6 +206,7 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => {
|
|||||||
async function createMetaTransactionAsync(
|
async function createMetaTransactionAsync(
|
||||||
data: string,
|
data: string,
|
||||||
value: BigNumber,
|
value: BigNumber,
|
||||||
|
fee?: BigNumber | number,
|
||||||
): Promise<SignedExchangeProxyMetaTransaction> {
|
): Promise<SignedExchangeProxyMetaTransaction> {
|
||||||
return signatureUtils.ecSignTypedDataExchangeProxyMetaTransactionAsync(
|
return signatureUtils.ecSignTypedDataExchangeProxyMetaTransactionAsync(
|
||||||
env.provider,
|
env.provider,
|
||||||
@ -218,7 +220,7 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => {
|
|||||||
salt: new BigNumber(hexUtils.random()),
|
salt: new BigNumber(hexUtils.random()),
|
||||||
callData: data,
|
callData: data,
|
||||||
feeToken: feeToken.address,
|
feeToken: feeToken.address,
|
||||||
feeAmount: getRandomPortion(TAKER_FEE_BALANCE),
|
feeAmount: fee !== undefined ? new BigNumber(fee) : getRandomPortion(TAKER_FEE_BALANCE),
|
||||||
domain: {
|
domain: {
|
||||||
chainId: 1,
|
chainId: 1,
|
||||||
name: 'ZeroEx',
|
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 () => {
|
it('can call `transformERC20()` with signed calldata and a relayer fee', async () => {
|
||||||
const swap = await generateSwapAsync();
|
const swap = await generateSwapAsync();
|
||||||
const callDataHash = hexUtils.hash(getSwapData(swap));
|
const callDataHash = hexUtils.hash(getSwapData(swap));
|
||||||
@ -301,4 +339,40 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => {
|
|||||||
'TransformerMetadata',
|
'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`",
|
"note": "Add `LogMetadataTransformer`",
|
||||||
"pr": 2657
|
"pr": 2657
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"note": "Add `IUniswapV2Feature`",
|
|
||||||
"pr": 2657
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"note": "Rename all feature contracts to have `Feature` suffix",
|
"note": "Rename all feature contracts to have `Feature` suffix",
|
||||||
"pr": 2657
|
"pr": 2657
|
||||||
@ -41,6 +37,18 @@
|
|||||||
{
|
{
|
||||||
"note": "Return `IZeroExContract` in `fullMigrateAsync()`",
|
"note": "Return `IZeroExContract` in `fullMigrateAsync()`",
|
||||||
"pr": 2657
|
"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
|
view
|
||||||
returns (bytes32 validCallDataHash)
|
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) {
|
if (signature.length == 0) {
|
||||||
return bytes32(0);
|
return bytes32(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ISignatureValidatorFeature(address(this)).isValidHashSignature(
|
if (ISignatureValidatorFeature(address(this)).isValidHashSignature(
|
||||||
callDataHash,
|
callDataHash,
|
||||||
getQuoteSigner(),
|
quoteSigner,
|
||||||
signature
|
signature
|
||||||
)) {
|
)) {
|
||||||
return callDataHash;
|
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 "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
||||||
import "../errors/LibTransformERC20RichErrors.sol";
|
import "../errors/LibTransformERC20RichErrors.sol";
|
||||||
import "../vendor/v3/IExchange.sol";
|
import "../vendor/v3/IExchange.sol";
|
||||||
import "../bridges/IBridgeAdapter.sol";
|
import "./bridges/IBridgeAdapter.sol";
|
||||||
import "./Transformer.sol";
|
import "./Transformer.sol";
|
||||||
import "./LibERC20Transformer.sol";
|
import "./LibERC20Transformer.sol";
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ contract FillQuoteTransformer is
|
|||||||
|
|
||||||
/// @dev Transform data to ABI-encode and pass into `transform()`.
|
/// @dev Transform data to ABI-encode and pass into `transform()`.
|
||||||
struct TransformData {
|
struct TransformData {
|
||||||
// Whether we aer performing a market sell or buy.
|
// Whether we are performing a market sell or buy.
|
||||||
Side side;
|
Side side;
|
||||||
// The token being sold.
|
// The token being sold.
|
||||||
// This should be an actual token, not the ETH pseudo-token.
|
// 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(1)`: Send to the taker.
|
||||||
// `address(2)`: Send to the sender (caller of `transformERC20()`).
|
// `address(2)`: Send to the sender (caller of `transformERC20()`).
|
||||||
address payable refundReceiver;
|
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()`.
|
/// @dev Results of a call to `_fillOrder()`.
|
||||||
@ -96,6 +99,7 @@ contract FillQuoteTransformer is
|
|||||||
uint256 soldAmount;
|
uint256 soldAmount;
|
||||||
uint256 protocolFee;
|
uint256 protocolFee;
|
||||||
uint256 takerTokenBalanceRemaining;
|
uint256 takerTokenBalanceRemaining;
|
||||||
|
bool isRfqtAllowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Emitted when a trade is skipped due to a lack of funds
|
/// @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`.
|
// Approve the ERC20 proxy to spend `sellToken`.
|
||||||
data.sellToken.approveIfBelow(erc20Proxy, data.fillAmount);
|
data.sellToken.approveIfBelow(erc20Proxy, data.fillAmount);
|
||||||
|
|
||||||
// Fill the orders.
|
|
||||||
state.protocolFee = exchange.protocolFeeMultiplier().safeMul(tx.gasprice);
|
state.protocolFee = exchange.protocolFeeMultiplier().safeMul(tx.gasprice);
|
||||||
state.ethRemaining = address(this).balance;
|
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) {
|
for (uint256 i = 0; i < data.orders.length; ++i) {
|
||||||
// Check if we've hit our targets.
|
// Check if we've hit our targets.
|
||||||
if (data.side == Side.Sell) {
|
if (data.side == Side.Sell) {
|
||||||
@ -434,6 +443,11 @@ contract FillQuoteTransformer is
|
|||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
} else {
|
} 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.
|
// Emit an event if we do not have sufficient ETH to cover the protocol fee.
|
||||||
if (state.ethRemaining < state.protocolFee) {
|
if (state.ethRemaining < state.protocolFee) {
|
||||||
emit ProtocolFeeUnfunded(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()`.
|
/// @dev Context information to pass into `transform()` by `TransformERC20.transformERC20()`.
|
||||||
struct TransformContext {
|
struct TransformContext {
|
||||||
// The hash of the `TransformERC20.transformERC20()` calldata.
|
// The hash of the `TransformERC20.transformERC20()` calldata.
|
||||||
|
// Will be null if the calldata is not signed.
|
||||||
bytes32 callDataHash;
|
bytes32 callDataHash;
|
||||||
// The caller of `TransformERC20.transformERC20()`.
|
// The caller of `TransformERC20.transformERC20()`.
|
||||||
address payable sender;
|
address payable sender;
|
||||||
|
@ -23,6 +23,7 @@ import "./mixins/MixinAdapterAddresses.sol";
|
|||||||
import "./mixins/MixinBalancer.sol";
|
import "./mixins/MixinBalancer.sol";
|
||||||
import "./mixins/MixinCurve.sol";
|
import "./mixins/MixinCurve.sol";
|
||||||
import "./mixins/MixinKyber.sol";
|
import "./mixins/MixinKyber.sol";
|
||||||
|
import "./mixins/MixinMooniswap.sol";
|
||||||
import "./mixins/MixinMStable.sol";
|
import "./mixins/MixinMStable.sol";
|
||||||
import "./mixins/MixinOasis.sol";
|
import "./mixins/MixinOasis.sol";
|
||||||
import "./mixins/MixinUniswap.sol";
|
import "./mixins/MixinUniswap.sol";
|
||||||
@ -34,6 +35,7 @@ contract BridgeAdapter is
|
|||||||
MixinBalancer,
|
MixinBalancer,
|
||||||
MixinCurve,
|
MixinCurve,
|
||||||
MixinKyber,
|
MixinKyber,
|
||||||
|
MixinMooniswap,
|
||||||
MixinMStable,
|
MixinMStable,
|
||||||
MixinOasis,
|
MixinOasis,
|
||||||
MixinUniswap,
|
MixinUniswap,
|
||||||
@ -44,6 +46,7 @@ contract BridgeAdapter is
|
|||||||
address private immutable BALANCER_BRIDGE_ADDRESS;
|
address private immutable BALANCER_BRIDGE_ADDRESS;
|
||||||
address private immutable CURVE_BRIDGE_ADDRESS;
|
address private immutable CURVE_BRIDGE_ADDRESS;
|
||||||
address private immutable KYBER_BRIDGE_ADDRESS;
|
address private immutable KYBER_BRIDGE_ADDRESS;
|
||||||
|
address private immutable MOONISWAP_BRIDGE_ADDRESS;
|
||||||
address private immutable MSTABLE_BRIDGE_ADDRESS;
|
address private immutable MSTABLE_BRIDGE_ADDRESS;
|
||||||
address private immutable OASIS_BRIDGE_ADDRESS;
|
address private immutable OASIS_BRIDGE_ADDRESS;
|
||||||
address private immutable UNISWAP_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 from The bridge address, indicating the underlying source of the fill.
|
||||||
/// @param to The `to` address, currrently `address(this)`
|
/// @param to The `to` address, currrently `address(this)`
|
||||||
event ERC20BridgeTransfer(
|
event ERC20BridgeTransfer(
|
||||||
address inputToken,
|
IERC20TokenV06 inputToken,
|
||||||
address outputToken,
|
IERC20TokenV06 outputToken,
|
||||||
uint256 inputTokenAmount,
|
uint256 inputTokenAmount,
|
||||||
uint256 outputTokenAmount,
|
uint256 outputTokenAmount,
|
||||||
address from,
|
address from,
|
||||||
@ -70,6 +73,7 @@ contract BridgeAdapter is
|
|||||||
MixinBalancer()
|
MixinBalancer()
|
||||||
MixinCurve()
|
MixinCurve()
|
||||||
MixinKyber(addresses)
|
MixinKyber(addresses)
|
||||||
|
MixinMooniswap(addresses)
|
||||||
MixinMStable(addresses)
|
MixinMStable(addresses)
|
||||||
MixinOasis(addresses)
|
MixinOasis(addresses)
|
||||||
MixinUniswap(addresses)
|
MixinUniswap(addresses)
|
||||||
@ -79,6 +83,7 @@ contract BridgeAdapter is
|
|||||||
BALANCER_BRIDGE_ADDRESS = addresses.balancerBridge;
|
BALANCER_BRIDGE_ADDRESS = addresses.balancerBridge;
|
||||||
CURVE_BRIDGE_ADDRESS = addresses.curveBridge;
|
CURVE_BRIDGE_ADDRESS = addresses.curveBridge;
|
||||||
KYBER_BRIDGE_ADDRESS = addresses.kyberBridge;
|
KYBER_BRIDGE_ADDRESS = addresses.kyberBridge;
|
||||||
|
MOONISWAP_BRIDGE_ADDRESS = addresses.mooniswapBridge;
|
||||||
MSTABLE_BRIDGE_ADDRESS = addresses.mStableBridge;
|
MSTABLE_BRIDGE_ADDRESS = addresses.mStableBridge;
|
||||||
OASIS_BRIDGE_ADDRESS = addresses.oasisBridge;
|
OASIS_BRIDGE_ADDRESS = addresses.oasisBridge;
|
||||||
UNISWAP_BRIDGE_ADDRESS = addresses.uniswapBridge;
|
UNISWAP_BRIDGE_ADDRESS = addresses.uniswapBridge;
|
||||||
@ -87,19 +92,19 @@ contract BridgeAdapter is
|
|||||||
|
|
||||||
function trade(
|
function trade(
|
||||||
bytes calldata makerAssetData,
|
bytes calldata makerAssetData,
|
||||||
address fromTokenAddress,
|
IERC20TokenV06 sellToken,
|
||||||
uint256 sellAmount
|
uint256 sellAmount
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
returns (uint256 boughtAmount)
|
returns (uint256 boughtAmount)
|
||||||
{
|
{
|
||||||
(
|
(
|
||||||
address toTokenAddress,
|
IERC20TokenV06 buyToken,
|
||||||
address bridgeAddress,
|
address bridgeAddress,
|
||||||
bytes memory bridgeData
|
bytes memory bridgeData
|
||||||
) = abi.decode(
|
) = abi.decode(
|
||||||
makerAssetData[4:],
|
makerAssetData[4:],
|
||||||
(address, address, bytes)
|
(IERC20TokenV06, address, bytes)
|
||||||
);
|
);
|
||||||
require(
|
require(
|
||||||
bridgeAddress != address(this) && bridgeAddress != address(0),
|
bridgeAddress != address(this) && bridgeAddress != address(0),
|
||||||
@ -108,65 +113,71 @@ contract BridgeAdapter is
|
|||||||
|
|
||||||
if (bridgeAddress == CURVE_BRIDGE_ADDRESS) {
|
if (bridgeAddress == CURVE_BRIDGE_ADDRESS) {
|
||||||
boughtAmount = _tradeCurve(
|
boughtAmount = _tradeCurve(
|
||||||
toTokenAddress,
|
buyToken,
|
||||||
sellAmount,
|
sellAmount,
|
||||||
bridgeData
|
bridgeData
|
||||||
);
|
);
|
||||||
} else if (bridgeAddress == UNISWAP_V2_BRIDGE_ADDRESS) {
|
} else if (bridgeAddress == UNISWAP_V2_BRIDGE_ADDRESS) {
|
||||||
boughtAmount = _tradeUniswapV2(
|
boughtAmount = _tradeUniswapV2(
|
||||||
toTokenAddress,
|
buyToken,
|
||||||
sellAmount,
|
sellAmount,
|
||||||
bridgeData
|
bridgeData
|
||||||
);
|
);
|
||||||
} else if (bridgeAddress == UNISWAP_BRIDGE_ADDRESS) {
|
} else if (bridgeAddress == UNISWAP_BRIDGE_ADDRESS) {
|
||||||
boughtAmount = _tradeUniswap(
|
boughtAmount = _tradeUniswap(
|
||||||
toTokenAddress,
|
buyToken,
|
||||||
sellAmount,
|
sellAmount,
|
||||||
bridgeData
|
bridgeData
|
||||||
);
|
);
|
||||||
} else if (bridgeAddress == BALANCER_BRIDGE_ADDRESS) {
|
} else if (bridgeAddress == BALANCER_BRIDGE_ADDRESS) {
|
||||||
boughtAmount = _tradeBalancer(
|
boughtAmount = _tradeBalancer(
|
||||||
toTokenAddress,
|
buyToken,
|
||||||
sellAmount,
|
sellAmount,
|
||||||
bridgeData
|
bridgeData
|
||||||
);
|
);
|
||||||
} else if (bridgeAddress == KYBER_BRIDGE_ADDRESS) {
|
} else if (bridgeAddress == KYBER_BRIDGE_ADDRESS) {
|
||||||
boughtAmount = _tradeKyber(
|
boughtAmount = _tradeKyber(
|
||||||
toTokenAddress,
|
buyToken,
|
||||||
|
sellAmount,
|
||||||
|
bridgeData
|
||||||
|
);
|
||||||
|
} else if (bridgeAddress == MOONISWAP_BRIDGE_ADDRESS) {
|
||||||
|
boughtAmount = _tradeMooniswap(
|
||||||
|
buyToken,
|
||||||
sellAmount,
|
sellAmount,
|
||||||
bridgeData
|
bridgeData
|
||||||
);
|
);
|
||||||
} else if (bridgeAddress == MSTABLE_BRIDGE_ADDRESS) {
|
} else if (bridgeAddress == MSTABLE_BRIDGE_ADDRESS) {
|
||||||
boughtAmount = _tradeMStable(
|
boughtAmount = _tradeMStable(
|
||||||
toTokenAddress,
|
buyToken,
|
||||||
sellAmount,
|
sellAmount,
|
||||||
bridgeData
|
bridgeData
|
||||||
);
|
);
|
||||||
} else if (bridgeAddress == OASIS_BRIDGE_ADDRESS) {
|
} else if (bridgeAddress == OASIS_BRIDGE_ADDRESS) {
|
||||||
boughtAmount = _tradeOasis(
|
boughtAmount = _tradeOasis(
|
||||||
toTokenAddress,
|
buyToken,
|
||||||
sellAmount,
|
sellAmount,
|
||||||
bridgeData
|
bridgeData
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
boughtAmount = _tradeZeroExBridge(
|
boughtAmount = _tradeZeroExBridge(
|
||||||
bridgeAddress,
|
bridgeAddress,
|
||||||
fromTokenAddress,
|
sellToken,
|
||||||
toTokenAddress,
|
buyToken,
|
||||||
sellAmount,
|
sellAmount,
|
||||||
bridgeData
|
bridgeData
|
||||||
);
|
);
|
||||||
|
// Do not emit an event. The bridge contract should emit one itself.
|
||||||
|
return boughtAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit ERC20BridgeTransfer(
|
emit ERC20BridgeTransfer(
|
||||||
fromTokenAddress,
|
sellToken,
|
||||||
toTokenAddress,
|
buyToken,
|
||||||
sellAmount,
|
sellAmount,
|
||||||
boughtAmount,
|
boughtAmount,
|
||||||
bridgeAddress,
|
bridgeAddress,
|
||||||
address(this)
|
address(this)
|
||||||
);
|
);
|
||||||
|
|
||||||
return boughtAmount;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,6 +26,7 @@ contract MixinAdapterAddresses
|
|||||||
address balancerBridge;
|
address balancerBridge;
|
||||||
address curveBridge;
|
address curveBridge;
|
||||||
address kyberBridge;
|
address kyberBridge;
|
||||||
|
address mooniswapBridge;
|
||||||
address mStableBridge;
|
address mStableBridge;
|
||||||
address oasisBridge;
|
address oasisBridge;
|
||||||
address uniswapBridge;
|
address uniswapBridge;
|
||||||
@ -36,6 +37,7 @@ contract MixinAdapterAddresses
|
|||||||
address uniswapV2Router;
|
address uniswapV2Router;
|
||||||
address uniswapExchangeFactory;
|
address uniswapExchangeFactory;
|
||||||
address mStable;
|
address mStable;
|
||||||
|
address mooniswapRegistry;
|
||||||
// Other
|
// Other
|
||||||
address weth;
|
address weth;
|
||||||
}
|
}
|
@ -32,9 +32,9 @@ interface IBalancerPool {
|
|||||||
/// @return spotPriceAfter The new marginal spot price of the given
|
/// @return spotPriceAfter The new marginal spot price of the given
|
||||||
/// token pair for this pool.
|
/// token pair for this pool.
|
||||||
function swapExactAmountIn(
|
function swapExactAmountIn(
|
||||||
address tokenIn,
|
IERC20TokenV06 tokenIn,
|
||||||
uint tokenAmountIn,
|
uint tokenAmountIn,
|
||||||
address tokenOut,
|
IERC20TokenV06 tokenOut,
|
||||||
uint minAmountOut,
|
uint minAmountOut,
|
||||||
uint maxPrice
|
uint maxPrice
|
||||||
) external returns (uint tokenAmountOut, uint spotPriceAfter);
|
) external returns (uint tokenAmountOut, uint spotPriceAfter);
|
||||||
@ -45,7 +45,7 @@ contract MixinBalancer {
|
|||||||
using LibERC20TokenV06 for IERC20TokenV06;
|
using LibERC20TokenV06 for IERC20TokenV06;
|
||||||
|
|
||||||
function _tradeBalancer(
|
function _tradeBalancer(
|
||||||
address toTokenAddress,
|
IERC20TokenV06 buyToken,
|
||||||
uint256 sellAmount,
|
uint256 sellAmount,
|
||||||
bytes memory bridgeData
|
bytes memory bridgeData
|
||||||
)
|
)
|
||||||
@ -53,21 +53,21 @@ contract MixinBalancer {
|
|||||||
returns (uint256 boughtAmount)
|
returns (uint256 boughtAmount)
|
||||||
{
|
{
|
||||||
// Decode the bridge data.
|
// Decode the bridge data.
|
||||||
(address fromTokenAddress, address poolAddress) = abi.decode(
|
(IERC20TokenV06 sellToken, IBalancerPool pool) = abi.decode(
|
||||||
bridgeData,
|
bridgeData,
|
||||||
(address, address)
|
(IERC20TokenV06, IBalancerPool)
|
||||||
);
|
);
|
||||||
IERC20TokenV06(fromTokenAddress).approveIfBelow(
|
sellToken.approveIfBelow(
|
||||||
poolAddress,
|
address(pool),
|
||||||
sellAmount
|
sellAmount
|
||||||
);
|
);
|
||||||
// Sell all of this contract's `fromTokenAddress` token balance.
|
// Sell all of this contract's `sellToken` token balance.
|
||||||
(boughtAmount,) = IBalancerPool(poolAddress).swapExactAmountIn(
|
(boughtAmount,) = pool.swapExactAmountIn(
|
||||||
fromTokenAddress, // tokenIn
|
sellToken, // tokenIn
|
||||||
sellAmount, // tokenAmountIn
|
sellAmount, // tokenAmountIn
|
||||||
toTokenAddress, // tokenOut
|
buyToken, // tokenOut
|
||||||
1, // minAmountOut
|
1, // minAmountOut
|
||||||
uint256(-1) // maxPrice
|
uint256(-1) // maxPrice
|
||||||
);
|
);
|
||||||
return boughtAmount;
|
return boughtAmount;
|
||||||
}
|
}
|
@ -34,13 +34,13 @@ contract MixinCurve {
|
|||||||
struct CurveBridgeData {
|
struct CurveBridgeData {
|
||||||
address curveAddress;
|
address curveAddress;
|
||||||
bytes4 exchangeFunctionSelector;
|
bytes4 exchangeFunctionSelector;
|
||||||
address fromTokenAddress;
|
IERC20TokenV06 sellToken;
|
||||||
int128 fromCoinIdx;
|
int128 fromCoinIdx;
|
||||||
int128 toCoinIdx;
|
int128 toCoinIdx;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _tradeCurve(
|
function _tradeCurve(
|
||||||
address toTokenAddress,
|
IERC20TokenV06 buyToken,
|
||||||
uint256 sellAmount,
|
uint256 sellAmount,
|
||||||
bytes memory bridgeData
|
bytes memory bridgeData
|
||||||
)
|
)
|
||||||
@ -49,8 +49,8 @@ contract MixinCurve {
|
|||||||
{
|
{
|
||||||
// Decode the bridge data to get the Curve metadata.
|
// Decode the bridge data to get the Curve metadata.
|
||||||
CurveBridgeData memory data = abi.decode(bridgeData, (CurveBridgeData));
|
CurveBridgeData memory data = abi.decode(bridgeData, (CurveBridgeData));
|
||||||
IERC20TokenV06(data.fromTokenAddress).approveIfBelow(data.curveAddress, sellAmount);
|
data.sellToken.approveIfBelow(data.curveAddress, sellAmount);
|
||||||
uint256 beforeBalance = IERC20TokenV06(toTokenAddress).balanceOf(address(this));
|
uint256 beforeBalance = buyToken.balanceOf(address(this));
|
||||||
(bool success, bytes memory resultData) =
|
(bool success, bytes memory resultData) =
|
||||||
data.curveAddress.call(abi.encodeWithSelector(
|
data.curveAddress.call(abi.encodeWithSelector(
|
||||||
data.exchangeFunctionSelector,
|
data.exchangeFunctionSelector,
|
||||||
@ -64,7 +64,6 @@ contract MixinCurve {
|
|||||||
if (!success) {
|
if (!success) {
|
||||||
resultData.rrevert();
|
resultData.rrevert();
|
||||||
}
|
}
|
||||||
|
return buyToken.balanceOf(address(this)).safeSub(beforeBalance);
|
||||||
return IERC20TokenV06(toTokenAddress).balanceOf(address(this)).safeSub(beforeBalance);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,34 +26,36 @@ import "./MixinAdapterAddresses.sol";
|
|||||||
|
|
||||||
interface IKyberNetworkProxy {
|
interface IKyberNetworkProxy {
|
||||||
|
|
||||||
/// @dev Sells `sellTokenAddress` tokens for `buyTokenAddress` tokens.
|
/// @dev Sells `sellTokenAddress` tokens for `buyTokenAddress` tokens
|
||||||
/// @param sellTokenAddress Token to sell.
|
/// using a hint for the reserve.
|
||||||
|
/// @param sellToken Token to sell.
|
||||||
/// @param sellAmount Amount of tokens 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 recipientAddress Address to send bought tokens to.
|
||||||
/// @param maxBuyTokenAmount A limit on the amount of tokens to buy.
|
/// @param maxBuyTokenAmount A limit on the amount of tokens to buy.
|
||||||
/// @param minConversionRate The minimal conversion rate. If actual rate
|
/// @param minConversionRate The minimal conversion rate. If actual rate
|
||||||
/// is lower, trade is canceled.
|
/// is lower, trade is canceled.
|
||||||
/// @param walletId The wallet ID to send part of the fees
|
/// @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.
|
/// @return boughtAmount Amount of tokens bought.
|
||||||
function trade(
|
function tradeWithHint(
|
||||||
address sellTokenAddress,
|
IERC20TokenV06 sellToken,
|
||||||
uint256 sellAmount,
|
uint256 sellAmount,
|
||||||
address buyTokenAddress,
|
IERC20TokenV06 buyToken,
|
||||||
address payable recipientAddress,
|
address payable recipientAddress,
|
||||||
uint256 maxBuyTokenAmount,
|
uint256 maxBuyTokenAmount,
|
||||||
uint256 minConversionRate,
|
uint256 minConversionRate,
|
||||||
address walletId
|
address payable walletId,
|
||||||
|
bytes calldata hint
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
payable
|
payable
|
||||||
returns(uint256 boughtAmount);
|
returns (uint256 boughtAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
contract MixinKyber is
|
contract MixinKyber is
|
||||||
MixinAdapterAddresses
|
MixinAdapterAddresses
|
||||||
{
|
{
|
||||||
|
|
||||||
using LibERC20TokenV06 for IERC20TokenV06;
|
using LibERC20TokenV06 for IERC20TokenV06;
|
||||||
|
|
||||||
/// @dev Address indicating the trade is using ETH
|
/// @dev Address indicating the trade is using ETH
|
||||||
@ -71,41 +73,39 @@ contract MixinKyber is
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _tradeKyber(
|
function _tradeKyber(
|
||||||
address toTokenAddress,
|
IERC20TokenV06 buyToken,
|
||||||
uint256 sellAmount,
|
uint256 sellAmount,
|
||||||
bytes memory bridgeData
|
bytes memory bridgeData
|
||||||
)
|
)
|
||||||
internal
|
internal
|
||||||
returns (uint256 boughtAmount)
|
returns (uint256 boughtAmount)
|
||||||
{
|
{
|
||||||
// Decode the bridge data to get the `fromTokenAddress`.
|
(IERC20TokenV06 sellToken, bytes memory hint) =
|
||||||
address fromTokenAddress = abi.decode(bridgeData, (address));
|
abi.decode(bridgeData, (IERC20TokenV06, bytes));
|
||||||
uint256 payableAmount;
|
|
||||||
|
|
||||||
if (fromTokenAddress != address(WETH)) {
|
uint256 payableAmount = 0;
|
||||||
|
if (sellToken != WETH) {
|
||||||
// If the input token is not WETH, grant an allowance to the exchange
|
// If the input token is not WETH, grant an allowance to the exchange
|
||||||
// to spend them.
|
// to spend them.
|
||||||
IERC20TokenV06(fromTokenAddress).approveIfBelow(
|
sellToken.approveIfBelow(
|
||||||
address(KYBER_NETWORK_PROXY),
|
address(KYBER_NETWORK_PROXY),
|
||||||
sellAmount
|
sellAmount
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// If the input token is WETH, unwrap it and attach it to the call.
|
// If the input token is WETH, unwrap it and attach it to the call.
|
||||||
fromTokenAddress = KYBER_ETH_ADDRESS;
|
|
||||||
payableAmount = sellAmount;
|
payableAmount = sellAmount;
|
||||||
WETH.withdraw(payableAmount);
|
WETH.withdraw(payableAmount);
|
||||||
}
|
}
|
||||||
bool isToTokenWeth = toTokenAddress == address(WETH);
|
|
||||||
|
|
||||||
// Try to sell all of this contract's input token balance through
|
// Try to sell all of this contract's input token balance through
|
||||||
// `KyberNetworkProxy.trade()`.
|
// `KyberNetworkProxy.trade()`.
|
||||||
boughtAmount = KYBER_NETWORK_PROXY.trade{ value: payableAmount }(
|
boughtAmount = KYBER_NETWORK_PROXY.tradeWithHint{ value: payableAmount }(
|
||||||
// Input token.
|
// Input token.
|
||||||
fromTokenAddress,
|
sellToken == WETH ? IERC20TokenV06(KYBER_ETH_ADDRESS) : sellToken,
|
||||||
// Sell amount.
|
// Sell amount.
|
||||||
sellAmount,
|
sellAmount,
|
||||||
// Output token.
|
// Output token.
|
||||||
isToTokenWeth ? KYBER_ETH_ADDRESS : toTokenAddress,
|
buyToken == WETH ? IERC20TokenV06(KYBER_ETH_ADDRESS) : buyToken,
|
||||||
// Transfer to this contract
|
// Transfer to this contract
|
||||||
address(uint160(address(this))),
|
address(uint160(address(this))),
|
||||||
// Buy as much as possible.
|
// Buy as much as possible.
|
||||||
@ -113,9 +113,11 @@ contract MixinKyber is
|
|||||||
// Lowest minimum conversion rate
|
// Lowest minimum conversion rate
|
||||||
1,
|
1,
|
||||||
// No affiliate address.
|
// No affiliate address.
|
||||||
address(0)
|
address(0),
|
||||||
|
hint
|
||||||
);
|
);
|
||||||
if (isToTokenWeth) {
|
// If receving ETH, wrap it to WETH.
|
||||||
|
if (buyToken == WETH) {
|
||||||
WETH.deposit{ value: boughtAmount }();
|
WETH.deposit{ value: boughtAmount }();
|
||||||
}
|
}
|
||||||
return boughtAmount;
|
return boughtAmount;
|
@ -27,19 +27,18 @@ import "./MixinAdapterAddresses.sol";
|
|||||||
interface IMStable {
|
interface IMStable {
|
||||||
|
|
||||||
function swap(
|
function swap(
|
||||||
address _input,
|
IERC20TokenV06 sellToken,
|
||||||
address _output,
|
IERC20TokenV06 buyToken,
|
||||||
uint256 _quantity,
|
uint256 sellAmount,
|
||||||
address _recipient
|
address recipient
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
returns (uint256 output);
|
returns (uint256 boughtAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
contract MixinMStable is
|
contract MixinMStable is
|
||||||
MixinAdapterAddresses
|
MixinAdapterAddresses
|
||||||
{
|
{
|
||||||
|
|
||||||
using LibERC20TokenV06 for IERC20TokenV06;
|
using LibERC20TokenV06 for IERC20TokenV06;
|
||||||
|
|
||||||
/// @dev Mainnet address of the mStable mUSD contract.
|
/// @dev Mainnet address of the mStable mUSD contract.
|
||||||
@ -52,21 +51,21 @@ contract MixinMStable is
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _tradeMStable(
|
function _tradeMStable(
|
||||||
address toTokenAddress,
|
IERC20TokenV06 buyToken,
|
||||||
uint256 sellAmount,
|
uint256 sellAmount,
|
||||||
bytes memory bridgeData
|
bytes memory bridgeData
|
||||||
)
|
)
|
||||||
internal
|
internal
|
||||||
returns (uint256 boughtAmount)
|
returns (uint256 boughtAmount)
|
||||||
{
|
{
|
||||||
// Decode the bridge data to get the `fromTokenAddress`.
|
// Decode the bridge data to get the `sellToken`.
|
||||||
(address fromTokenAddress) = abi.decode(bridgeData, (address));
|
(IERC20TokenV06 sellToken) = abi.decode(bridgeData, (IERC20TokenV06));
|
||||||
// Grant an allowance to the exchange to spend `fromTokenAddress` token.
|
// Grant an allowance to the exchange to spend `sellToken` token.
|
||||||
IERC20TokenV06(fromTokenAddress).approveIfBelow(address(MSTABLE), sellAmount);
|
sellToken.approveIfBelow(address(MSTABLE), sellAmount);
|
||||||
|
|
||||||
boughtAmount = MSTABLE.swap(
|
boughtAmount = MSTABLE.swap(
|
||||||
fromTokenAddress,
|
sellToken,
|
||||||
toTokenAddress,
|
buyToken,
|
||||||
sellAmount,
|
sellAmount,
|
||||||
address(this)
|
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 {
|
interface IOasis {
|
||||||
|
|
||||||
/// @dev Sell `sellAmount` of `fromToken` token and receive `toToken` token.
|
/// @dev Sell `sellAmount` of `sellToken` token and receive `buyToken` token.
|
||||||
/// @param fromToken The token being sold.
|
/// @param sellToken The token being sold.
|
||||||
/// @param sellAmount The amount of `fromToken` token being sold.
|
/// @param sellAmount The amount of `sellToken` token being sold.
|
||||||
/// @param toToken The token being bought.
|
/// @param buyToken The token being bought.
|
||||||
/// @param minFillAmount Minimum amount of `toToken` token to buy.
|
/// @param minBoughtAmount Minimum amount of `buyToken` token to buy.
|
||||||
/// @return fillAmount Amount of `toToken` bought.
|
/// @return boughtAmount Amount of `buyToken` bought.
|
||||||
function sellAllAmount(
|
function sellAllAmount(
|
||||||
address fromToken,
|
IERC20TokenV06 sellToken,
|
||||||
uint256 sellAmount,
|
uint256 sellAmount,
|
||||||
address toToken,
|
IERC20TokenV06 buyToken,
|
||||||
uint256 minFillAmount
|
uint256 minBoughtAmount
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
returns (uint256 fillAmount);
|
returns (uint256 boughtAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
contract MixinOasis is
|
contract MixinOasis is
|
||||||
MixinAdapterAddresses
|
MixinAdapterAddresses
|
||||||
{
|
{
|
||||||
|
|
||||||
using LibERC20TokenV06 for IERC20TokenV06;
|
using LibERC20TokenV06 for IERC20TokenV06;
|
||||||
|
|
||||||
/// @dev Mainnet address of the Oasis `MatchingMarket` contract.
|
/// @dev Mainnet address of the Oasis `MatchingMarket` contract.
|
||||||
@ -57,25 +56,25 @@ contract MixinOasis is
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _tradeOasis(
|
function _tradeOasis(
|
||||||
address toTokenAddress,
|
IERC20TokenV06 buyToken,
|
||||||
uint256 sellAmount,
|
uint256 sellAmount,
|
||||||
bytes memory bridgeData
|
bytes memory bridgeData
|
||||||
)
|
)
|
||||||
internal
|
internal
|
||||||
returns (uint256 boughtAmount)
|
returns (uint256 boughtAmount)
|
||||||
{
|
{
|
||||||
// Decode the bridge data to get the `fromTokenAddress`.
|
// Decode the bridge data to get the `sellToken`.
|
||||||
(address fromTokenAddress) = abi.decode(bridgeData, (address));
|
(IERC20TokenV06 sellToken) = abi.decode(bridgeData, (IERC20TokenV06));
|
||||||
// Grant an allowance to the exchange to spend `fromTokenAddress` token.
|
// Grant an allowance to the exchange to spend `sellToken` token.
|
||||||
IERC20TokenV06(fromTokenAddress).approveIfBelow(
|
sellToken.approveIfBelow(
|
||||||
address(OASIS),
|
address(OASIS),
|
||||||
sellAmount
|
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(
|
boughtAmount = OASIS.sellAllAmount(
|
||||||
fromTokenAddress,
|
sellToken,
|
||||||
sellAmount,
|
sellAmount,
|
||||||
toTokenAddress,
|
buyToken,
|
||||||
// min fill amount
|
// min fill amount
|
||||||
1
|
1
|
||||||
);
|
);
|
@ -27,11 +27,11 @@ import "./MixinAdapterAddresses.sol";
|
|||||||
interface IUniswapExchangeFactory {
|
interface IUniswapExchangeFactory {
|
||||||
|
|
||||||
/// @dev Get the exchange for a token.
|
/// @dev Get the exchange for a token.
|
||||||
/// @param tokenAddress The address of the token contract.
|
/// @param token The token contract.
|
||||||
function getExchange(address tokenAddress)
|
function getExchange(IERC20TokenV06 token)
|
||||||
external
|
external
|
||||||
view
|
view
|
||||||
returns (address);
|
returns (IUniswapExchange exchange);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IUniswapExchange {
|
interface IUniswapExchange {
|
||||||
@ -71,7 +71,7 @@ interface IUniswapExchange {
|
|||||||
/// @param minEthBought The minimum amount of intermediate ETH to buy.
|
/// @param minEthBought The minimum amount of intermediate ETH to buy.
|
||||||
/// @param deadline Time when this order expires.
|
/// @param deadline Time when this order expires.
|
||||||
/// @param recipient Who to transfer the tokens to.
|
/// @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.
|
/// @return tokensBought Amount of tokens bought.
|
||||||
function tokenToTokenTransferInput(
|
function tokenToTokenTransferInput(
|
||||||
uint256 tokensSold,
|
uint256 tokensSold,
|
||||||
@ -79,7 +79,7 @@ interface IUniswapExchange {
|
|||||||
uint256 minEthBought,
|
uint256 minEthBought,
|
||||||
uint256 deadline,
|
uint256 deadline,
|
||||||
address recipient,
|
address recipient,
|
||||||
address toTokenAddress
|
IERC20TokenV06 buyToken
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
returns (uint256 tokensBought);
|
returns (uint256 tokensBought);
|
||||||
@ -89,14 +89,14 @@ interface IUniswapExchange {
|
|||||||
/// @param minTokensBought The minimum number of tokens to buy.
|
/// @param minTokensBought The minimum number of tokens to buy.
|
||||||
/// @param minEthBought The minimum amount of intermediate ETH to buy.
|
/// @param minEthBought The minimum amount of intermediate ETH to buy.
|
||||||
/// @param deadline Time when this order expires.
|
/// @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.
|
/// @return tokensBought Amount of tokens bought.
|
||||||
function tokenToTokenSwapInput(
|
function tokenToTokenSwapInput(
|
||||||
uint256 tokensSold,
|
uint256 tokensSold,
|
||||||
uint256 minTokensBought,
|
uint256 minTokensBought,
|
||||||
uint256 minEthBought,
|
uint256 minEthBought,
|
||||||
uint256 deadline,
|
uint256 deadline,
|
||||||
address toTokenAddress
|
IERC20TokenV06 buyToken
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
returns (uint256 tokensBought);
|
returns (uint256 tokensBought);
|
||||||
@ -105,7 +105,6 @@ interface IUniswapExchange {
|
|||||||
contract MixinUniswap is
|
contract MixinUniswap is
|
||||||
MixinAdapterAddresses
|
MixinAdapterAddresses
|
||||||
{
|
{
|
||||||
|
|
||||||
using LibERC20TokenV06 for IERC20TokenV06;
|
using LibERC20TokenV06 for IERC20TokenV06;
|
||||||
|
|
||||||
/// @dev Mainnet address of the WETH contract.
|
/// @dev Mainnet address of the WETH contract.
|
||||||
@ -121,27 +120,27 @@ contract MixinUniswap is
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _tradeUniswap(
|
function _tradeUniswap(
|
||||||
address toTokenAddress,
|
IERC20TokenV06 buyToken,
|
||||||
uint256 sellAmount,
|
uint256 sellAmount,
|
||||||
bytes memory bridgeData
|
bytes memory bridgeData
|
||||||
)
|
)
|
||||||
internal
|
internal
|
||||||
returns (uint256 boughtAmount)
|
returns (uint256 boughtAmount)
|
||||||
{
|
{
|
||||||
// Decode the bridge data to get the `fromTokenAddress`.
|
// Decode the bridge data to get the `sellToken`.
|
||||||
(address fromTokenAddress) = abi.decode(bridgeData, (address));
|
(IERC20TokenV06 sellToken) = abi.decode(bridgeData, (IERC20TokenV06));
|
||||||
|
|
||||||
// Get the exchange for the token pair.
|
// Get the exchange for the token pair.
|
||||||
IUniswapExchange exchange = _getUniswapExchangeForTokenPair(
|
IUniswapExchange exchange = _getUniswapExchangeForTokenPair(
|
||||||
fromTokenAddress,
|
sellToken,
|
||||||
toTokenAddress
|
buyToken
|
||||||
);
|
);
|
||||||
|
|
||||||
// Convert from WETH to a token.
|
// Convert from WETH to a token.
|
||||||
if (fromTokenAddress == address(WETH)) {
|
if (sellToken == WETH) {
|
||||||
// Unwrap the WETH.
|
// Unwrap the WETH.
|
||||||
WETH.withdraw(sellAmount);
|
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 }(
|
boughtAmount = exchange.ethToTokenTransferInput{ value: sellAmount }(
|
||||||
// Minimum buy amount.
|
// Minimum buy amount.
|
||||||
1,
|
1,
|
||||||
@ -152,13 +151,13 @@ contract MixinUniswap is
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Convert from a token to WETH.
|
// Convert from a token to WETH.
|
||||||
} else if (toTokenAddress == address(WETH)) {
|
} else if (buyToken == WETH) {
|
||||||
// Grant the exchange an allowance.
|
// Grant the exchange an allowance.
|
||||||
IERC20TokenV06(fromTokenAddress).approveIfBelow(
|
sellToken.approveIfBelow(
|
||||||
address(exchange),
|
address(exchange),
|
||||||
sellAmount
|
sellAmount
|
||||||
);
|
);
|
||||||
// Buy as much ETH with `fromTokenAddress` token as possible.
|
// Buy as much ETH with `sellToken` token as possible.
|
||||||
boughtAmount = exchange.tokenToEthSwapInput(
|
boughtAmount = exchange.tokenToEthSwapInput(
|
||||||
// Sell all tokens we hold.
|
// Sell all tokens we hold.
|
||||||
sellAmount,
|
sellAmount,
|
||||||
@ -172,11 +171,11 @@ contract MixinUniswap is
|
|||||||
// Convert from one token to another.
|
// Convert from one token to another.
|
||||||
} else {
|
} else {
|
||||||
// Grant the exchange an allowance.
|
// Grant the exchange an allowance.
|
||||||
IERC20TokenV06(fromTokenAddress).approveIfBelow(
|
sellToken.approveIfBelow(
|
||||||
address(exchange),
|
address(exchange),
|
||||||
sellAmount
|
sellAmount
|
||||||
);
|
);
|
||||||
// Buy as much `toTokenAddress` token with `fromTokenAddress` token
|
// Buy as much `buyToken` token with `sellToken` token
|
||||||
boughtAmount = exchange.tokenToTokenSwapInput(
|
boughtAmount = exchange.tokenToTokenSwapInput(
|
||||||
// Sell all tokens we hold.
|
// Sell all tokens we hold.
|
||||||
sellAmount,
|
sellAmount,
|
||||||
@ -186,8 +185,8 @@ contract MixinUniswap is
|
|||||||
1,
|
1,
|
||||||
// Expires after this block.
|
// Expires after this block.
|
||||||
block.timestamp,
|
block.timestamp,
|
||||||
// Convert to `toTokenAddress`.
|
// Convert to `buyToken`.
|
||||||
toTokenAddress
|
buyToken
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,24 +196,21 @@ contract MixinUniswap is
|
|||||||
/// @dev Retrieves the uniswap exchange for a given token pair.
|
/// @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 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.
|
/// 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 sellToken The address of the token we are converting from.
|
||||||
/// @param toTokenAddress The address of the token we are converting to.
|
/// @param buyToken The address of the token we are converting to.
|
||||||
/// @return exchange The uniswap exchange.
|
/// @return exchange The uniswap exchange.
|
||||||
function _getUniswapExchangeForTokenPair(
|
function _getUniswapExchangeForTokenPair(
|
||||||
address fromTokenAddress,
|
IERC20TokenV06 sellToken,
|
||||||
address toTokenAddress
|
IERC20TokenV06 buyToken
|
||||||
)
|
)
|
||||||
private
|
private
|
||||||
view
|
view
|
||||||
returns (IUniswapExchange exchange)
|
returns (IUniswapExchange exchange)
|
||||||
{
|
{
|
||||||
address exchangeTokenAddress = fromTokenAddress;
|
|
||||||
// Whichever isn't WETH is the exchange token.
|
// Whichever isn't WETH is the exchange token.
|
||||||
if (fromTokenAddress == address(WETH)) {
|
exchange = sellToken == WETH
|
||||||
exchangeTokenAddress = toTokenAddress;
|
? UNISWAP_EXCHANGE_FACTORY.getExchange(buyToken)
|
||||||
}
|
: UNISWAP_EXCHANGE_FACTORY.getExchange(sellToken);
|
||||||
exchange = IUniswapExchange(UNISWAP_EXCHANGE_FACTORY.getExchange(exchangeTokenAddress));
|
|
||||||
require(address(exchange) != address(0), "NO_UNISWAP_EXCHANGE_FOR_TOKEN");
|
require(address(exchange) != address(0), "NO_UNISWAP_EXCHANGE_FOR_TOKEN");
|
||||||
return exchange;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -50,7 +50,6 @@ interface IUniswapV2Router02 {
|
|||||||
contract MixinUniswapV2 is
|
contract MixinUniswapV2 is
|
||||||
MixinAdapterAddresses
|
MixinAdapterAddresses
|
||||||
{
|
{
|
||||||
|
|
||||||
using LibERC20TokenV06 for IERC20TokenV06;
|
using LibERC20TokenV06 for IERC20TokenV06;
|
||||||
|
|
||||||
/// @dev Mainnet address of the `UniswapV2Router02` contract.
|
/// @dev Mainnet address of the `UniswapV2Router02` contract.
|
||||||
@ -63,21 +62,23 @@ contract MixinUniswapV2 is
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _tradeUniswapV2(
|
function _tradeUniswapV2(
|
||||||
address toTokenAddress,
|
IERC20TokenV06 buyToken,
|
||||||
uint256 sellAmount,
|
uint256 sellAmount,
|
||||||
bytes memory bridgeData
|
bytes memory bridgeData
|
||||||
)
|
)
|
||||||
internal
|
internal
|
||||||
returns (uint256)
|
returns (uint256 boughtAmount)
|
||||||
{
|
{
|
||||||
// Decode the bridge data to get the `fromTokenAddress`.
|
|
||||||
// solhint-disable indent
|
// solhint-disable indent
|
||||||
address[] memory path = abi.decode(bridgeData, (address[]));
|
address[] memory path = abi.decode(bridgeData, (address[]));
|
||||||
// solhint-enable indent
|
// solhint-enable indent
|
||||||
|
|
||||||
require(path.length >= 2, "UniswapV2Bridge/PATH_LENGTH_MUST_BE_AT_LEAST_TWO");
|
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");
|
require(
|
||||||
// Grant the Uniswap router an allowance.
|
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(
|
IERC20TokenV06(path[0]).approveIfBelow(
|
||||||
address(UNISWAP_V2_ROUTER),
|
address(UNISWAP_V2_ROUTER),
|
||||||
sellAmount
|
sellAmount
|
||||||
@ -88,7 +89,7 @@ contract MixinUniswapV2 is
|
|||||||
sellAmount,
|
sellAmount,
|
||||||
// Minimum buy amount.
|
// Minimum buy amount.
|
||||||
1,
|
1,
|
||||||
// Convert `fromTokenAddress` to `toTokenAddress`.
|
// Convert to `buyToken` along this path.
|
||||||
path,
|
path,
|
||||||
// Recipient is `this`.
|
// Recipient is `this`.
|
||||||
address(this),
|
address(this),
|
@ -24,15 +24,15 @@ import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
|||||||
|
|
||||||
interface IERC20Bridge {
|
interface IERC20Bridge {
|
||||||
|
|
||||||
/// @dev Transfers `amount` of the ERC20 `tokenAddress` from `from` to `to`.
|
/// @dev Transfers `amount` of the ERC20 `buyToken` from `from` to `to`.
|
||||||
/// @param tokenAddress The address of the ERC20 token to transfer.
|
/// @param buyToken The address of the ERC20 token to transfer.
|
||||||
/// @param from Address to transfer asset from.
|
/// @param from Address to transfer asset from.
|
||||||
/// @param to Address to transfer asset to.
|
/// @param to Address to transfer asset to.
|
||||||
/// @param amount Amount of asset to transfer.
|
/// @param amount Amount of asset to transfer.
|
||||||
/// @param bridgeData Arbitrary asset data needed by the bridge contract.
|
/// @param bridgeData Arbitrary asset data needed by the bridge contract.
|
||||||
/// @return success The magic bytes `0xdc1600f3` if successful.
|
/// @return success The magic bytes `0xdc1600f3` if successful.
|
||||||
function bridgeTransferFrom(
|
function bridgeTransferFrom(
|
||||||
address tokenAddress,
|
IERC20TokenV06 buyToken,
|
||||||
address from,
|
address from,
|
||||||
address to,
|
address to,
|
||||||
uint256 amount,
|
uint256 amount,
|
||||||
@ -49,28 +49,27 @@ contract MixinZeroExBridge {
|
|||||||
|
|
||||||
function _tradeZeroExBridge(
|
function _tradeZeroExBridge(
|
||||||
address bridgeAddress,
|
address bridgeAddress,
|
||||||
address fromTokenAddress,
|
IERC20TokenV06 sellToken,
|
||||||
address toTokenAddress,
|
IERC20TokenV06 buyToken,
|
||||||
uint256 sellAmount,
|
uint256 sellAmount,
|
||||||
bytes memory bridgeData
|
bytes memory bridgeData
|
||||||
)
|
)
|
||||||
internal
|
internal
|
||||||
returns (uint256 boughtAmount)
|
returns (uint256 boughtAmount)
|
||||||
{
|
{
|
||||||
uint256 balanceBefore = IERC20TokenV06(toTokenAddress).balanceOf(address(this));
|
uint256 balanceBefore = buyToken.balanceOf(address(this));
|
||||||
// Trade the good old fashioned way
|
// Trade the good old fashioned way
|
||||||
IERC20TokenV06(fromTokenAddress).compatTransfer(
|
sellToken.compatTransfer(
|
||||||
bridgeAddress,
|
bridgeAddress,
|
||||||
sellAmount
|
sellAmount
|
||||||
);
|
);
|
||||||
IERC20Bridge(bridgeAddress).bridgeTransferFrom(
|
IERC20Bridge(bridgeAddress).bridgeTransferFrom(
|
||||||
toTokenAddress,
|
buyToken,
|
||||||
bridgeAddress,
|
address(bridgeAddress),
|
||||||
address(this),
|
address(this),
|
||||||
1, // amount to transfer back from the bridge
|
1, // amount to transfer back from the bridge
|
||||||
bridgeData
|
bridgeData
|
||||||
);
|
);
|
||||||
|
boughtAmount = buyToken.balanceOf(address(this)).safeSub(balanceBefore);
|
||||||
boughtAmount = IERC20TokenV06(toTokenAddress).balanceOf(address(this)).safeSub(balanceBefore);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -41,7 +41,7 @@
|
|||||||
"config": {
|
"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",
|
"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: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": {
|
"repository": {
|
||||||
"type": "git",
|
"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 MixinBalancer from '../test/generated-artifacts/MixinBalancer.json';
|
||||||
import * as MixinCurve from '../test/generated-artifacts/MixinCurve.json';
|
import * as MixinCurve from '../test/generated-artifacts/MixinCurve.json';
|
||||||
import * as MixinKyber from '../test/generated-artifacts/MixinKyber.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 MixinMStable from '../test/generated-artifacts/MixinMStable.json';
|
||||||
import * as MixinOasis from '../test/generated-artifacts/MixinOasis.json';
|
import * as MixinOasis from '../test/generated-artifacts/MixinOasis.json';
|
||||||
import * as MixinUniswap from '../test/generated-artifacts/MixinUniswap.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 = {
|
export const artifacts = {
|
||||||
IZeroEx: IZeroEx as ContractArtifact,
|
IZeroEx: IZeroEx as ContractArtifact,
|
||||||
ZeroEx: ZeroEx 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,
|
LibCommonRichErrors: LibCommonRichErrors as ContractArtifact,
|
||||||
LibMetaTransactionsRichErrors: LibMetaTransactionsRichErrors as ContractArtifact,
|
LibMetaTransactionsRichErrors: LibMetaTransactionsRichErrors as ContractArtifact,
|
||||||
LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact,
|
LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact,
|
||||||
@ -164,6 +154,18 @@ export const artifacts = {
|
|||||||
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
|
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
|
||||||
Transformer: Transformer as ContractArtifact,
|
Transformer: Transformer as ContractArtifact,
|
||||||
WethTransformer: WethTransformer 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,
|
IERC20Bridge: IERC20Bridge as ContractArtifact,
|
||||||
IExchange: IExchange as ContractArtifact,
|
IExchange: IExchange as ContractArtifact,
|
||||||
IGasToken: IGasToken as ContractArtifact,
|
IGasToken: IGasToken as ContractArtifact,
|
||||||
|
@ -28,11 +28,11 @@ import {
|
|||||||
TransformERC20FeatureEvents,
|
TransformERC20FeatureEvents,
|
||||||
} from '../wrappers';
|
} from '../wrappers';
|
||||||
|
|
||||||
const { NULL_BYTES, NULL_BYTES32 } = constants;
|
const { NULL_ADDRESS, NULL_BYTES, NULL_BYTES32 } = constants;
|
||||||
|
|
||||||
type MintTokenTransformerEvent = DecodedLogEntry<TestMintTokenERC20TransformerMintTransformEventArgs>;
|
type MintTokenTransformerEvent = DecodedLogEntry<TestMintTokenERC20TransformerMintTransformEventArgs>;
|
||||||
|
|
||||||
blockchainTests.resets('TransformERC20 feature', env => {
|
blockchainTests.resets.only('TransformERC20 feature', env => {
|
||||||
const callDataSignerKey = hexUtils.random();
|
const callDataSignerKey = hexUtils.random();
|
||||||
const callDataSigner = ethjs.bufferToHex(ethjs.privateToAddress(ethjs.toBuffer(callDataSignerKey)));
|
const callDataSigner = ethjs.bufferToHex(ethjs.privateToAddress(ethjs.toBuffer(callDataSignerKey)));
|
||||||
let owner: string;
|
let owner: string;
|
||||||
@ -687,6 +687,37 @@ blockchainTests.resets('TransformERC20 feature', env => {
|
|||||||
expect(actualCallDataHash).to.eq(hexUtils.hash(callData));
|
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 () => {
|
it('passes empty calldata hash to transformer with improperly signed calldata', async () => {
|
||||||
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
|
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
|
||||||
const startingInputTokenBalance = getRandomInteger(0, '100e18');
|
const startingInputTokenBalance = getRandomInteger(0, '100e18');
|
||||||
|
@ -62,6 +62,7 @@ blockchainTests.resets('FillQuoteTransformer', env => {
|
|||||||
balancerBridge: NULL_ADDRESS,
|
balancerBridge: NULL_ADDRESS,
|
||||||
curveBridge: NULL_ADDRESS,
|
curveBridge: NULL_ADDRESS,
|
||||||
kyberBridge: NULL_ADDRESS,
|
kyberBridge: NULL_ADDRESS,
|
||||||
|
mooniswapBridge: NULL_ADDRESS,
|
||||||
mStableBridge: NULL_ADDRESS,
|
mStableBridge: NULL_ADDRESS,
|
||||||
oasisBridge: NULL_ADDRESS,
|
oasisBridge: NULL_ADDRESS,
|
||||||
uniswapBridge: NULL_ADDRESS,
|
uniswapBridge: NULL_ADDRESS,
|
||||||
@ -71,6 +72,7 @@ blockchainTests.resets('FillQuoteTransformer', env => {
|
|||||||
uniswapV2Router: NULL_ADDRESS,
|
uniswapV2Router: NULL_ADDRESS,
|
||||||
uniswapExchangeFactory: NULL_ADDRESS,
|
uniswapExchangeFactory: NULL_ADDRESS,
|
||||||
mStable: NULL_ADDRESS,
|
mStable: NULL_ADDRESS,
|
||||||
|
mooniswapRegistry: NULL_ADDRESS,
|
||||||
weth: NULL_ADDRESS,
|
weth: NULL_ADDRESS,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -273,6 +275,7 @@ blockchainTests.resets('FillQuoteTransformer', env => {
|
|||||||
maxOrderFillAmounts: [],
|
maxOrderFillAmounts: [],
|
||||||
fillAmount: MAX_UINT256,
|
fillAmount: MAX_UINT256,
|
||||||
refundReceiver: NULL_ADDRESS,
|
refundReceiver: NULL_ADDRESS,
|
||||||
|
rfqtTakerAddress: NULL_ADDRESS,
|
||||||
...fields,
|
...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_curve';
|
||||||
export * from '../test/generated-wrappers/mixin_kyber';
|
export * from '../test/generated-wrappers/mixin_kyber';
|
||||||
export * from '../test/generated-wrappers/mixin_m_stable';
|
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_oasis';
|
||||||
export * from '../test/generated-wrappers/mixin_uniswap';
|
export * from '../test/generated-wrappers/mixin_uniswap';
|
||||||
export * from '../test/generated-wrappers/mixin_uniswap_v2';
|
export * from '../test/generated-wrappers/mixin_uniswap_v2';
|
||||||
|
@ -82,6 +82,7 @@
|
|||||||
"test/generated-artifacts/MixinCurve.json",
|
"test/generated-artifacts/MixinCurve.json",
|
||||||
"test/generated-artifacts/MixinKyber.json",
|
"test/generated-artifacts/MixinKyber.json",
|
||||||
"test/generated-artifacts/MixinMStable.json",
|
"test/generated-artifacts/MixinMStable.json",
|
||||||
|
"test/generated-artifacts/MixinMooniswap.json",
|
||||||
"test/generated-artifacts/MixinOasis.json",
|
"test/generated-artifacts/MixinOasis.json",
|
||||||
"test/generated-artifacts/MixinUniswap.json",
|
"test/generated-artifacts/MixinUniswap.json",
|
||||||
"test/generated-artifacts/MixinUniswapV2.json",
|
"test/generated-artifacts/MixinUniswapV2.json",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user