feat: asset-swapper mooniswap (#2675)

* feat: asset-swapper sampler early exit

* feat: asset-swapper mooniswap

* tests and linter

* deploy to mainnet

* CHANGELOGs

* fix excluded sources difference

* typo
This commit is contained in:
Jacob Evans 2020-08-24 11:21:01 +10:00 committed by GitHub
parent af78238507
commit cc31445189
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 519 additions and 58 deletions

View File

@ -17,6 +17,10 @@
{
"note": "Added `MStableBridge`",
"pr": 2662
},
{
"note": "Added `MooniswapBridge`",
"pr": 2675
}
]
},

View File

@ -0,0 +1,149 @@
/*
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.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
import "@0x/contracts-exchange-libs/contracts/src/IWallet.sol";
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
import "../interfaces/IERC20Bridge.sol";
import "../interfaces/IMooniswap.sol";
// solhint-disable space-after-comma
// solhint-disable not-rely-on-time
contract MooniswapBridge is
IERC20Bridge,
IWallet,
DeploymentConstants
{
struct TransferState {
IMooniswap pool;
uint256 fromTokenBalance;
IEtherToken weth;
uint256 boughtAmount;
address fromTokenAddress;
address toTokenAddress;
}
// solhint-disable no-empty-blocks
/// @dev Payable fallback to receive ETH from uniswap.
function ()
external
payable
{}
/// @dev Callback for `IERC20Bridge`. Tries to buy `amount` of
/// `toTokenAddress` tokens by selling the entirety of the `fromTokenAddress`
/// token encoded in the bridge data.
/// @param toTokenAddress The token to buy and transfer to `to`.
/// @param from The maker (this contract).
/// @param to The recipient of the bought tokens.
/// @param amount Minimum amount of `toTokenAddress` tokens to buy.
/// @param bridgeData The abi-encoded path of token addresses. Last element must be toTokenAddress
/// @return success The magic bytes if successful.
function bridgeTransferFrom(
address toTokenAddress,
address from,
address to,
uint256 amount,
bytes calldata bridgeData
)
external
returns (bytes4 success)
{
// State memory object to avoid stack overflows.
TransferState memory state;
// Decode the bridge data to get the `fromTokenAddress`.
address fromTokenAddress = abi.decode(bridgeData, (address));
// Get the weth contract.
state.weth = IEtherToken(_getWethAddress());
// Get our balance of `fromTokenAddress` token.
state.fromTokenBalance = IERC20Token(fromTokenAddress).balanceOf(address(this));
state.fromTokenAddress = fromTokenAddress == address(state.weth) ? address(0) : fromTokenAddress;
state.toTokenAddress = toTokenAddress == address(state.weth) ? address(0) : toTokenAddress;
state.pool = IMooniswap(
IMooniswapRegistry(_getMooniswapAddress()).pools(
state.fromTokenAddress,
state.toTokenAddress
)
);
// withdraw WETH to ETH
if (state.fromTokenAddress == address(0)) {
state.weth.withdraw(state.fromTokenBalance);
} else {
// Grant the pool an allowance.
LibERC20Token.approveIfBelow(
state.fromTokenAddress,
address(state.pool),
state.fromTokenBalance
);
}
uint256 ethValue = state.fromTokenAddress == address(0) ? state.fromTokenBalance : 0;
state.boughtAmount = state.pool.swap.value(ethValue)(
state.fromTokenAddress,
state.toTokenAddress,
state.fromTokenBalance,
amount,
address(0)
);
// Deposit to WETH
if (state.toTokenAddress == address(0)) {
state.weth.deposit.value(state.boughtAmount)();
}
// Transfer funds to `to`
IERC20Token(toTokenAddress).transfer(to, state.boughtAmount);
emit ERC20BridgeTransfer(
// input token
fromTokenAddress,
// output token
toTokenAddress,
// input token amount
state.fromTokenBalance,
// output token amount
state.boughtAmount,
from,
to
);
return BRIDGE_SUCCESS;
}
/// @dev `SignatureType.Wallet` callback, so that this bridge can be the maker
/// and sign for itself in orders. Always succeeds.
/// @return magicValue Success bytes, always.
function isValidSignature(
bytes32,
bytes calldata
)
external
view
returns (bytes4 magicValue)
{
return LEGACY_WALLET_MAGIC_VALUE;
}
}

View File

@ -0,0 +1,40 @@
/*
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.5.9;
interface IMooniswapRegistry {
function pools(address token1, address token2) external view returns(address);
}
interface IMooniswap {
function swap(
address fromToken,
address destToken,
uint256 amount,
uint256 minReturn,
address referral
)
external
payable
returns(uint256 returnAmount);
}

View File

@ -38,7 +38,7 @@
"docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
},
"config": {
"abis": "./test/generated-artifacts/@(BalancerBridge|BancorBridge|ChaiBridge|CurveBridge|DexForwarderBridge|DydxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IBalancerPool|IBancorNetwork|IChai|ICurve|IDydx|IDydxBridge|IERC20Bridge|IEth2Dai|IGasToken|IKyberNetworkProxy|IMStable|IUniswapExchange|IUniswapExchangeFactory|IUniswapV2Router01|KyberBridge|MStableBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MixinGasToken|MultiAssetProxy|Ownable|StaticCallProxy|TestBancorBridge|TestChaiBridge|TestDexForwarderBridge|TestDydxBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|TestUniswapV2Bridge|UniswapBridge|UniswapV2Bridge).json",
"abis": "./test/generated-artifacts/@(BalancerBridge|BancorBridge|ChaiBridge|CurveBridge|DexForwarderBridge|DydxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IBalancerPool|IBancorNetwork|IChai|ICurve|IDydx|IDydxBridge|IERC20Bridge|IEth2Dai|IGasToken|IKyberNetworkProxy|IMStable|IMooniswap|IUniswapExchange|IUniswapExchangeFactory|IUniswapV2Router01|KyberBridge|MStableBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MixinGasToken|MooniswapBridge|MultiAssetProxy|Ownable|StaticCallProxy|TestBancorBridge|TestChaiBridge|TestDexForwarderBridge|TestDydxBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|TestUniswapV2Bridge|UniswapBridge|UniswapV2Bridge).json",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
},
"repository": {

View File

@ -30,6 +30,7 @@ import * as IERC20Bridge from '../generated-artifacts/IERC20Bridge.json';
import * as IEth2Dai from '../generated-artifacts/IEth2Dai.json';
import * as IGasToken from '../generated-artifacts/IGasToken.json';
import * as IKyberNetworkProxy from '../generated-artifacts/IKyberNetworkProxy.json';
import * as IMooniswap from '../generated-artifacts/IMooniswap.json';
import * as IMStable from '../generated-artifacts/IMStable.json';
import * as IUniswapExchange from '../generated-artifacts/IUniswapExchange.json';
import * as IUniswapExchangeFactory from '../generated-artifacts/IUniswapExchangeFactory.json';
@ -38,6 +39,7 @@ import * as KyberBridge from '../generated-artifacts/KyberBridge.json';
import * as MixinAssetProxyDispatcher from '../generated-artifacts/MixinAssetProxyDispatcher.json';
import * as MixinAuthorizable from '../generated-artifacts/MixinAuthorizable.json';
import * as MixinGasToken from '../generated-artifacts/MixinGasToken.json';
import * as MooniswapBridge from '../generated-artifacts/MooniswapBridge.json';
import * as MStableBridge from '../generated-artifacts/MStableBridge.json';
import * as MultiAssetProxy from '../generated-artifacts/MultiAssetProxy.json';
import * as Ownable from '../generated-artifacts/Ownable.json';
@ -74,6 +76,7 @@ export const artifacts = {
KyberBridge: KyberBridge as ContractArtifact,
MStableBridge: MStableBridge as ContractArtifact,
MixinGasToken: MixinGasToken as ContractArtifact,
MooniswapBridge: MooniswapBridge as ContractArtifact,
UniswapBridge: UniswapBridge as ContractArtifact,
UniswapV2Bridge: UniswapV2Bridge as ContractArtifact,
IAssetData: IAssetData as ContractArtifact,
@ -91,6 +94,7 @@ export const artifacts = {
IGasToken: IGasToken as ContractArtifact,
IKyberNetworkProxy: IKyberNetworkProxy as ContractArtifact,
IMStable: IMStable as ContractArtifact,
IMooniswap: IMooniswap as ContractArtifact,
IUniswapExchange: IUniswapExchange as ContractArtifact,
IUniswapExchangeFactory: IUniswapExchangeFactory as ContractArtifact,
IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact,

View File

@ -29,6 +29,7 @@ export * from '../generated-wrappers/i_eth2_dai';
export * from '../generated-wrappers/i_gas_token';
export * from '../generated-wrappers/i_kyber_network_proxy';
export * from '../generated-wrappers/i_m_stable';
export * from '../generated-wrappers/i_mooniswap';
export * from '../generated-wrappers/i_uniswap_exchange';
export * from '../generated-wrappers/i_uniswap_exchange_factory';
export * from '../generated-wrappers/i_uniswap_v2_router01';
@ -37,6 +38,7 @@ export * from '../generated-wrappers/m_stable_bridge';
export * from '../generated-wrappers/mixin_asset_proxy_dispatcher';
export * from '../generated-wrappers/mixin_authorizable';
export * from '../generated-wrappers/mixin_gas_token';
export * from '../generated-wrappers/mooniswap_bridge';
export * from '../generated-wrappers/multi_asset_proxy';
export * from '../generated-wrappers/ownable';
export * from '../generated-wrappers/static_call_proxy';

View File

@ -30,6 +30,7 @@ import * as IERC20Bridge from '../test/generated-artifacts/IERC20Bridge.json';
import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json';
import * as IGasToken from '../test/generated-artifacts/IGasToken.json';
import * as IKyberNetworkProxy from '../test/generated-artifacts/IKyberNetworkProxy.json';
import * as IMooniswap from '../test/generated-artifacts/IMooniswap.json';
import * as IMStable from '../test/generated-artifacts/IMStable.json';
import * as IUniswapExchange from '../test/generated-artifacts/IUniswapExchange.json';
import * as IUniswapExchangeFactory from '../test/generated-artifacts/IUniswapExchangeFactory.json';
@ -38,6 +39,7 @@ import * as KyberBridge from '../test/generated-artifacts/KyberBridge.json';
import * as MixinAssetProxyDispatcher from '../test/generated-artifacts/MixinAssetProxyDispatcher.json';
import * as MixinAuthorizable from '../test/generated-artifacts/MixinAuthorizable.json';
import * as MixinGasToken from '../test/generated-artifacts/MixinGasToken.json';
import * as MooniswapBridge from '../test/generated-artifacts/MooniswapBridge.json';
import * as MStableBridge from '../test/generated-artifacts/MStableBridge.json';
import * as MultiAssetProxy from '../test/generated-artifacts/MultiAssetProxy.json';
import * as Ownable from '../test/generated-artifacts/Ownable.json';
@ -74,6 +76,7 @@ export const artifacts = {
KyberBridge: KyberBridge as ContractArtifact,
MStableBridge: MStableBridge as ContractArtifact,
MixinGasToken: MixinGasToken as ContractArtifact,
MooniswapBridge: MooniswapBridge as ContractArtifact,
UniswapBridge: UniswapBridge as ContractArtifact,
UniswapV2Bridge: UniswapV2Bridge as ContractArtifact,
IAssetData: IAssetData as ContractArtifact,
@ -91,6 +94,7 @@ export const artifacts = {
IGasToken: IGasToken as ContractArtifact,
IKyberNetworkProxy: IKyberNetworkProxy as ContractArtifact,
IMStable: IMStable as ContractArtifact,
IMooniswap: IMooniswap as ContractArtifact,
IUniswapExchange: IUniswapExchange as ContractArtifact,
IUniswapExchangeFactory: IUniswapExchangeFactory as ContractArtifact,
IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact,

View File

@ -29,6 +29,7 @@ export * from '../test/generated-wrappers/i_eth2_dai';
export * from '../test/generated-wrappers/i_gas_token';
export * from '../test/generated-wrappers/i_kyber_network_proxy';
export * from '../test/generated-wrappers/i_m_stable';
export * from '../test/generated-wrappers/i_mooniswap';
export * from '../test/generated-wrappers/i_uniswap_exchange';
export * from '../test/generated-wrappers/i_uniswap_exchange_factory';
export * from '../test/generated-wrappers/i_uniswap_v2_router01';
@ -37,6 +38,7 @@ export * from '../test/generated-wrappers/m_stable_bridge';
export * from '../test/generated-wrappers/mixin_asset_proxy_dispatcher';
export * from '../test/generated-wrappers/mixin_authorizable';
export * from '../test/generated-wrappers/mixin_gas_token';
export * from '../test/generated-wrappers/mooniswap_bridge';
export * from '../test/generated-wrappers/multi_asset_proxy';
export * from '../test/generated-wrappers/ownable';
export * from '../test/generated-wrappers/static_call_proxy';

View File

@ -29,6 +29,7 @@
"generated-artifacts/IGasToken.json",
"generated-artifacts/IKyberNetworkProxy.json",
"generated-artifacts/IMStable.json",
"generated-artifacts/IMooniswap.json",
"generated-artifacts/IUniswapExchange.json",
"generated-artifacts/IUniswapExchangeFactory.json",
"generated-artifacts/IUniswapV2Router01.json",
@ -37,6 +38,7 @@
"generated-artifacts/MixinAssetProxyDispatcher.json",
"generated-artifacts/MixinAuthorizable.json",
"generated-artifacts/MixinGasToken.json",
"generated-artifacts/MooniswapBridge.json",
"generated-artifacts/MultiAssetProxy.json",
"generated-artifacts/Ownable.json",
"generated-artifacts/StaticCallProxy.json",
@ -78,6 +80,7 @@
"test/generated-artifacts/IGasToken.json",
"test/generated-artifacts/IKyberNetworkProxy.json",
"test/generated-artifacts/IMStable.json",
"test/generated-artifacts/IMooniswap.json",
"test/generated-artifacts/IUniswapExchange.json",
"test/generated-artifacts/IUniswapExchangeFactory.json",
"test/generated-artifacts/IUniswapV2Router01.json",
@ -86,6 +89,7 @@
"test/generated-artifacts/MixinAssetProxyDispatcher.json",
"test/generated-artifacts/MixinAuthorizable.json",
"test/generated-artifacts/MixinGasToken.json",
"test/generated-artifacts/MooniswapBridge.json",
"test/generated-artifacts/MultiAssetProxy.json",
"test/generated-artifacts/Ownable.json",
"test/generated-artifacts/StaticCallProxy.json",

View File

@ -52,6 +52,8 @@ contract DeploymentConstants {
address constant private GST_COLLECTOR_ADDRESS = 0x000000D3b08566BE75A6DB803C03C85C0c1c5B96;
/// @dev Mainnet address of the mStable mUSD contract.
address constant private MUSD_ADDRESS = 0xe2f2a5C287993345a840Db3B0845fbC70f5935a5;
/// @dev Mainnet address of the Mooniswap Registry contract
address constant private MOONISWAP_REGISTRY = 0x71CD6666064C3A1354a3B4dca5fA1E2D3ee7D303;
// // Ropsten addresses ///////////////////////////////////////////////////////
// /// @dev Mainnet address of the WETH contract.
@ -272,4 +274,14 @@ contract DeploymentConstants {
{
return MUSD_ADDRESS;
}
/// @dev An overridable way to retrieve the Mooniswap registry address.
/// @return musd The Mooniswap registry address.
function _getMooniswapAddress()
internal
view
returns (address registry)
{
return MOONISWAP_REGISTRY;
}
}

View File

@ -57,6 +57,10 @@
{
"note": "Merge `erc20-bridge-sampler` into this package",
"pr": 2664
},
{
"note": "Added `Mooniswap`",
"pr": 2675
}
]
},

View File

@ -70,7 +70,9 @@ contract CurveSampler is
uint256 buyAmount = 0;
if (didSucceed) {
buyAmount = abi.decode(resultData, (uint256));
} else {
}
// Exit early if the amount is too high for the source to serve
if (buyAmount == 0) {
break;
}
makerTokenAmounts[i] = buyAmount;
@ -119,7 +121,9 @@ contract CurveSampler is
uint256 sellAmount = 0;
if (didSucceed) {
sellAmount = abi.decode(resultData, (uint256));
} else {
}
// Exit early if the amount is too high for the source to serve
if (sellAmount == 0) {
break;
}
takerTokenAmounts[i] = sellAmount;

View File

@ -25,6 +25,7 @@ import "./KyberSampler.sol";
import "./LiquidityProviderSampler.sol";
import "./MultiBridgeSampler.sol";
import "./MStableSampler.sol";
import "./MooniswapSampler.sol";
import "./NativeOrderSampler.sol";
import "./UniswapSampler.sol";
import "./UniswapV2Sampler.sol";
@ -36,6 +37,7 @@ contract ERC20BridgeSampler is
KyberSampler,
LiquidityProviderSampler,
MStableSampler,
MooniswapSampler,
MultiBridgeSampler,
NativeOrderSampler,
UniswapSampler,

View File

@ -61,46 +61,15 @@ contract Eth2DaiSampler is
uint256 buyAmount = 0;
if (didSucceed) {
buyAmount = abi.decode(resultData, (uint256));
} else{
}
// Exit early if the amount is too high for the source to serve
if (buyAmount == 0) {
break;
}
makerTokenAmounts[i] = buyAmount;
}
}
/// @dev Sample sell quotes from Eth2Dai/Oasis using a hop to an intermediate token.
/// I.e WBTC/DAI via ETH or WBTC/ETH via DAI
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param intermediateToken Address of the token to hop to.
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromEth2DaiHop(
address takerToken,
address makerToken,
address intermediateToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (uint256[] memory makerTokenAmounts)
{
if (makerToken == intermediateToken || takerToken == intermediateToken) {
return makerTokenAmounts;
}
uint256[] memory intermediateAmounts = sampleSellsFromEth2Dai(
takerToken,
intermediateToken,
takerTokenAmounts
);
makerTokenAmounts = sampleSellsFromEth2Dai(
intermediateToken,
makerToken,
intermediateAmounts
);
}
/// @dev Sample buy quotes from Eth2Dai/Oasis.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
@ -131,7 +100,9 @@ contract Eth2DaiSampler is
uint256 sellAmount = 0;
if (didSucceed) {
sellAmount = abi.decode(resultData, (uint256));
} else {
}
// Exit early if the amount is too high for the source to serve
if (sellAmount == 0) {
break;
}
takerTokenAmounts[i] = sellAmount;

View File

@ -0,0 +1,37 @@
/*
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.5.9;
interface IMooniswapRegistry {
function pools(address token1, address token2) external view returns(address);
}
interface IMooniswap {
function getReturn(
address fromToken,
address destToken,
uint256 amount
)
external
view
returns(uint256 returnAmount);
}

View File

@ -77,8 +77,9 @@ contract LiquidityProviderSampler is
uint256 buyAmount = 0;
if (didSucceed) {
buyAmount = abi.decode(resultData, (uint256));
} else {
// Exit early if the amount is too high for the liquidity provider to serve
}
// Exit early if the amount is too high for the source to serve
if (buyAmount == 0) {
break;
}
makerTokenAmounts[i] = buyAmount;

View File

@ -65,7 +65,9 @@ contract MStableSampler is
uint256 buyAmount = 0;
if (didSucceed) {
(, , buyAmount) = abi.decode(resultData, (bool, string, uint256));
} else {
}
// Exit early if the amount is too high for the source to serve
if (buyAmount == 0) {
break;
}
makerTokenAmounts[i] = buyAmount;

View File

@ -0,0 +1,146 @@
/*
Copyright 2019 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.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
import "./IMooniswap.sol";
import "./ApproximateBuys.sol";
import "./SamplerUtils.sol";
contract MooniswapSampler is
DeploymentConstants,
SamplerUtils,
ApproximateBuys
{
/// @dev Gas limit for Mooniswap calls.
uint256 constant private MOONISWAP_CALL_GAS = 150e3; // 150k
/// @dev Sample sell quotes from Mooniswap.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromMooniswap(
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (uint256[] memory makerTokenAmounts)
{
_assertValidPair(makerToken, takerToken);
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
address _takerToken = takerToken == _getWethAddress() ? address(0) : takerToken;
address _makerToken = makerToken == _getWethAddress() ? address(0) : makerToken;
// Find the pool for the pair, ETH is represented
// as address(0)
IMooniswap pool = IMooniswap(
IMooniswapRegistry(_getMooniswapAddress()).pools(_takerToken, _makerToken)
);
// If there is no pool then return early
if (address(pool) == address(0)) {
return makerTokenAmounts;
}
uint256 poolBalance = _takerToken == address(0) ? address(pool).balance : IERC20Token(_takerToken).balanceOf(address(pool));
for (uint256 i = 0; i < numSamples; i++) {
// If the pool balance is smaller than the sell amount
// don't sample to avoid multiplication overflow in buys
if (poolBalance < takerTokenAmounts[i]) {
break;
}
(bool didSucceed, bytes memory resultData) =
address(pool).staticcall.gas(MOONISWAP_CALL_GAS)(
abi.encodeWithSelector(
IMooniswap(0).getReturn.selector,
_takerToken,
_makerToken,
takerTokenAmounts[i]
));
uint256 buyAmount = 0;
if (didSucceed) {
buyAmount = abi.decode(resultData, (uint256));
}
// Exit early if the amount is too high for the source to serve
if (buyAmount == 0) {
break;
}
makerTokenAmounts[i] = buyAmount;
}
}
/// @dev Sample buy quotes from Mooniswap.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token sell amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromMooniswap(
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (uint256[] memory takerTokenAmounts)
{
return _sampleApproximateBuys(
ApproximateBuyQuoteOpts({
makerTokenData: abi.encode(makerToken),
takerTokenData: abi.encode(takerToken),
getSellQuoteCallback: _sampleSellForApproximateBuyFromMooniswap
}),
makerTokenAmounts
);
}
function _sampleSellForApproximateBuyFromMooniswap(
bytes memory takerTokenData,
bytes memory makerTokenData,
uint256 sellAmount
)
private
view
returns (uint256 buyAmount)
{
(address takerToken) =
abi.decode(takerTokenData, (address));
(address makerToken) =
abi.decode(makerTokenData, (address));
(bool success, bytes memory resultData) =
address(this).staticcall(abi.encodeWithSelector(
this.sampleSellsFromMooniswap.selector,
takerToken,
makerToken,
_toSingleValueArray(sellAmount)
));
if (!success) {
return 0;
}
// solhint-disable-next-line indent
return abi.decode(resultData, (uint256[]))[0];
}
}

View File

@ -69,10 +69,12 @@ contract MultiBridgeSampler {
uint256 buyAmount = 0;
if (didSucceed) {
buyAmount = abi.decode(resultData, (uint256));
} else {
// Exit early if the amount is too high for the liquidity provider to serve
}
// Exit early if the amount is too high for the source to serve
if (buyAmount == 0) {
break;
}
makerTokenAmounts[i] = buyAmount;
}
}

View File

@ -10,6 +10,7 @@
"scripts": {
"build": "yarn pre_build && tsc -b",
"watch": "tsc -w -p tsconfig.json",
"watch:contracts": "sol-compiler -w",
"build:ci": "yarn build",
"pre_build": "run-s compile contracts:gen generate_contract_wrappers contracts:copy",
"compile": "sol-compiler",
@ -36,7 +37,7 @@
"config": {
"publicInterfaceContracts": "ERC20BridgeSampler,ILiquidityProvider,ILiquidityProviderRegistry,DummyLiquidityProviderRegistry,DummyLiquidityProvider",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(ApproximateBuys|CurveSampler|DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|Eth2DaiSampler|ICurve|IEth2Dai|IKyberHintHandler|IKyberNetwork|IKyberNetworkProxy|IKyberStorage|ILiquidityProvider|ILiquidityProviderRegistry|IMStable|IMultiBridge|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|TestERC20BridgeSampler|TestNativeOrderSampler|UniswapSampler|UniswapV2Sampler).json",
"abis": "./test/generated-artifacts/@(ApproximateBuys|CurveSampler|DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|Eth2DaiSampler|ICurve|IEth2Dai|IKyberHintHandler|IKyberNetwork|IKyberNetworkProxy|IKyberStorage|ILiquidityProvider|ILiquidityProviderRegistry|IMStable|IMooniswap|IMultiBridge|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|TestERC20BridgeSampler|TestNativeOrderSampler|UniswapSampler|UniswapV2Sampler).json",
"postpublish": {
"assets": []
}

View File

@ -2,7 +2,7 @@ import { ContractAddresses, getContractAddressesForChainOrThrow } from '@0x/cont
import { DevUtilsContract } from '@0x/contract-wrappers';
import { schemas } from '@0x/json-schemas';
import { assetDataUtils, SignedOrder } from '@0x/order-utils';
import { MeshOrderProviderOpts, Orderbook, SRAPollingOrderProviderOpts } from '@0x/orderbook';
import { APIOrder, MeshOrderProviderOpts, Orderbook, SRAPollingOrderProviderOpts } from '@0x/orderbook';
import { BigNumber, providerUtils } from '@0x/utils';
import { BlockParamLiteral, SupportedProvider, ZeroExProvider } from 'ethereum-types';
import * as _ from 'lodash';
@ -422,7 +422,7 @@ export class SwapQuoter {
const takerAssetData = assetDataUtils.encodeERC20AssetData(takerTokenAddress);
let [sellOrders, buyOrders] =
options.excludedSources && options.excludedSources.includes(ERC20BridgeSource.Native)
? Promise.resolve([[], []])
? await Promise.resolve([[] as APIOrder[], [] as APIOrder[]])
: await Promise.all([
this.orderbook.getOrdersAsync(makerAssetData, takerAssetData),
this.orderbook.getOrdersAsync(takerAssetData, makerAssetData),

View File

@ -16,6 +16,7 @@ export const SELL_SOURCES = [
ERC20BridgeSource.Balancer,
// ERC20BridgeSource.Bancor, // FIXME: Disabled until Bancor SDK supports batch requests
ERC20BridgeSource.MStable,
ERC20BridgeSource.Mooniswap,
];
/**
@ -30,6 +31,7 @@ export const BUY_SOURCES = [
ERC20BridgeSource.Balancer,
// ERC20BridgeSource.Bancor, // FIXME: Disabled until Bancor SDK supports buy quotes
ERC20BridgeSource.MStable,
ERC20BridgeSource.Mooniswap,
];
export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
@ -49,12 +51,7 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
/**
* Sources to poll for ETH fee price estimates.
*/
export const FEE_QUOTE_SOURCES = [
ERC20BridgeSource.Uniswap,
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.Eth2Dai,
ERC20BridgeSource.Kyber,
];
export const FEE_QUOTE_SOURCES = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.UniswapV2];
/**
* Mainnet Curve configuration

View File

@ -106,7 +106,7 @@ export class MarketOperationUtils {
),
// Get ETH -> maker token price.
await DexOrderSampler.ops.getMedianSellRateAsync(
difference(FEE_QUOTE_SOURCES.concat(this._optionalSources()), _opts.excludedSources),
difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
makerToken,
this._wethAddress,
ONE_ETHER,
@ -118,7 +118,7 @@ export class MarketOperationUtils {
),
// Get ETH -> taker token price.
await DexOrderSampler.ops.getMedianSellRateAsync(
difference(FEE_QUOTE_SOURCES.concat(this._optionalSources()), _opts.excludedSources),
difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
takerToken,
this._wethAddress,
ONE_ETHER,
@ -225,7 +225,7 @@ export class MarketOperationUtils {
),
// Get ETH -> maker token price.
await DexOrderSampler.ops.getMedianSellRateAsync(
difference(FEE_QUOTE_SOURCES.concat(this._optionalSources()), _opts.excludedSources),
difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
makerToken,
this._wethAddress,
ONE_ETHER,
@ -236,7 +236,7 @@ export class MarketOperationUtils {
),
// Get ETH -> taker token price.
await DexOrderSampler.ops.getMedianSellRateAsync(
difference(FEE_QUOTE_SOURCES.concat(this._optionalSources()), _opts.excludedSources),
difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
takerToken,
this._wethAddress,
ONE_ETHER,

View File

@ -201,6 +201,8 @@ function getBridgeAddressFromFill(fill: CollapsedFill, opts: CreateOrderFromPath
return (fill.fillData as MultiBridgeFillData).poolAddress;
case ERC20BridgeSource.MStable:
return opts.contractAddresses.mStableBridge;
case ERC20BridgeSource.Mooniswap:
return opts.contractAddresses.mooniswapBridge;
default:
break;
}

View File

@ -365,7 +365,7 @@ export const samplerOperations = {
source: ERC20BridgeSource.MStable,
encodeCall: contract => {
return contract
.sampleSellsFromMStable(makerToken, takerToken, takerFillAmounts)
.sampleSellsFromMStable(takerToken, makerToken, takerFillAmounts)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
@ -380,7 +380,7 @@ export const samplerOperations = {
source: ERC20BridgeSource.MStable,
encodeCall: contract => {
return contract
.sampleBuysFromMStable(makerToken, takerToken, makerFillAmounts)
.sampleBuysFromMStable(takerToken, makerToken, makerFillAmounts)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
@ -390,6 +390,40 @@ export const samplerOperations = {
},
};
},
getMooniswapSellQuotes(
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
): SourceQuoteOperation {
return {
source: ERC20BridgeSource.Mooniswap,
encodeCall: contract => {
return contract
.sampleSellsFromMooniswap(takerToken, makerToken, takerFillAmounts)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract
.getABIDecodedReturnData<BigNumber[]>('sampleSellsFromMooniswap', callResults)
.map(amount => ({ amount }));
},
};
},
getMooniswapBuyQuotes(makerToken: string, takerToken: string, makerFillAmounts: BigNumber[]): SourceQuoteOperation {
return {
source: ERC20BridgeSource.Mooniswap,
encodeCall: contract => {
return contract
.sampleBuysFromMooniswap(takerToken, makerToken, makerFillAmounts)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract
.getABIDecodedReturnData<BigNumber[]>('sampleBuysFromMooniswap', callResults)
.map(amount => ({ amount }));
},
};
},
getMedianSellRateAsync: async (
sources: ERC20BridgeSource[],
makerToken: string,
@ -570,6 +604,12 @@ export const samplerOperations = {
);
case ERC20BridgeSource.MStable:
return samplerOperations.getMStableSellQuotes(makerToken, takerToken, takerFillAmounts);
case ERC20BridgeSource.Mooniswap:
return samplerOperations.getMooniswapSellQuotes(
makerToken,
takerToken,
takerFillAmounts,
);
default:
throw new Error(`Unsupported sell sample source: ${source}`);
}
@ -690,6 +730,12 @@ export const samplerOperations = {
return []; // FIXME: Waiting for Bancor SDK to support buy quotes, but don't throw an error here
case ERC20BridgeSource.MStable:
return samplerOperations.getMStableBuyQuotes(makerToken, takerToken, makerFillAmounts);
case ERC20BridgeSource.Mooniswap:
return samplerOperations.getMooniswapBuyQuotes(
makerToken,
takerToken,
makerFillAmounts,
);
default:
throw new Error(`Unsupported buy sample source: ${source}`);
}

View File

@ -40,6 +40,7 @@ export enum ERC20BridgeSource {
Balancer = 'Balancer',
Bancor = 'Bancor',
MStable = 'mStable',
Mooniswap = 'Mooniswap',
}
// tslint:disable: enum-naming

View File

@ -19,12 +19,14 @@ import * as IKyberNetworkProxy from '../test/generated-artifacts/IKyberNetworkPr
import * as IKyberStorage from '../test/generated-artifacts/IKyberStorage.json';
import * as ILiquidityProvider from '../test/generated-artifacts/ILiquidityProvider.json';
import * as ILiquidityProviderRegistry from '../test/generated-artifacts/ILiquidityProviderRegistry.json';
import * as IMooniswap from '../test/generated-artifacts/IMooniswap.json';
import * as IMStable from '../test/generated-artifacts/IMStable.json';
import * as IMultiBridge from '../test/generated-artifacts/IMultiBridge.json';
import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json';
import * as IUniswapV2Router01 from '../test/generated-artifacts/IUniswapV2Router01.json';
import * as KyberSampler from '../test/generated-artifacts/KyberSampler.json';
import * as LiquidityProviderSampler from '../test/generated-artifacts/LiquidityProviderSampler.json';
import * as MooniswapSampler from '../test/generated-artifacts/MooniswapSampler.json';
import * as MStableSampler from '../test/generated-artifacts/MStableSampler.json';
import * as MultiBridgeSampler from '../test/generated-artifacts/MultiBridgeSampler.json';
import * as NativeOrderSampler from '../test/generated-artifacts/NativeOrderSampler.json';
@ -49,12 +51,14 @@ export const artifacts = {
ILiquidityProvider: ILiquidityProvider as ContractArtifact,
ILiquidityProviderRegistry: ILiquidityProviderRegistry as ContractArtifact,
IMStable: IMStable as ContractArtifact,
IMooniswap: IMooniswap as ContractArtifact,
IMultiBridge: IMultiBridge as ContractArtifact,
IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact,
IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact,
KyberSampler: KyberSampler as ContractArtifact,
LiquidityProviderSampler: LiquidityProviderSampler as ContractArtifact,
MStableSampler: MStableSampler as ContractArtifact,
MooniswapSampler: MooniswapSampler as ContractArtifact,
MultiBridgeSampler: MultiBridgeSampler as ContractArtifact,
NativeOrderSampler: NativeOrderSampler as ContractArtifact,
SamplerUtils: SamplerUtils as ContractArtifact,

View File

@ -86,6 +86,8 @@ describe('MarketOperationUtils tests', () => {
return ERC20BridgeSource.Curve;
case contractAddresses.mStableBridge.toLowerCase():
return ERC20BridgeSource.MStable;
case contractAddresses.mooniswapBridge.toLowerCase():
return ERC20BridgeSource.Mooniswap;
default:
break;
}
@ -293,6 +295,7 @@ describe('MarketOperationUtils tests', () => {
[ERC20BridgeSource.LiquidityProvider]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.MultiBridge]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.MStable]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Mooniswap]: _.times(NUM_SAMPLES, () => 0),
};
interface FillDataBySource {
@ -430,6 +433,7 @@ describe('MarketOperationUtils tests', () => {
ERC20BridgeSource.Curve,
ERC20BridgeSource.Balancer,
ERC20BridgeSource.MStable,
ERC20BridgeSource.Mooniswap,
],
allowFallback: false,
shouldBatchBridgeOrders: false,
@ -842,6 +846,7 @@ describe('MarketOperationUtils tests', () => {
ERC20BridgeSource.Curve,
ERC20BridgeSource.Balancer,
ERC20BridgeSource.MStable,
ERC20BridgeSource.Mooniswap,
],
allowFallback: false,
shouldBatchBridgeOrders: false,

View File

@ -18,12 +18,14 @@ export * from '../test/generated-wrappers/i_kyber_storage';
export * from '../test/generated-wrappers/i_liquidity_provider';
export * from '../test/generated-wrappers/i_liquidity_provider_registry';
export * from '../test/generated-wrappers/i_m_stable';
export * from '../test/generated-wrappers/i_mooniswap';
export * from '../test/generated-wrappers/i_multi_bridge';
export * from '../test/generated-wrappers/i_uniswap_exchange_quotes';
export * from '../test/generated-wrappers/i_uniswap_v2_router01';
export * from '../test/generated-wrappers/kyber_sampler';
export * from '../test/generated-wrappers/liquidity_provider_sampler';
export * from '../test/generated-wrappers/m_stable_sampler';
export * from '../test/generated-wrappers/mooniswap_sampler';
export * from '../test/generated-wrappers/multi_bridge_sampler';
export * from '../test/generated-wrappers/native_order_sampler';
export * from '../test/generated-wrappers/sampler_utils';

View File

@ -23,12 +23,14 @@
"test/generated-artifacts/ILiquidityProvider.json",
"test/generated-artifacts/ILiquidityProviderRegistry.json",
"test/generated-artifacts/IMStable.json",
"test/generated-artifacts/IMooniswap.json",
"test/generated-artifacts/IMultiBridge.json",
"test/generated-artifacts/IUniswapExchangeQuotes.json",
"test/generated-artifacts/IUniswapV2Router01.json",
"test/generated-artifacts/KyberSampler.json",
"test/generated-artifacts/LiquidityProviderSampler.json",
"test/generated-artifacts/MStableSampler.json",
"test/generated-artifacts/MooniswapSampler.json",
"test/generated-artifacts/MultiBridgeSampler.json",
"test/generated-artifacts/NativeOrderSampler.json",
"test/generated-artifacts/SamplerUtils.json",

View File

@ -29,6 +29,10 @@
{
"note": "Deploy `MStableBridge` on Mainnet",
"pr": 2662
},
{
"note": "Deploy `MooniswapBridge` on Mainnet",
"pr": 2675
}
]
},

View File

@ -41,6 +41,7 @@
"exchangeProxyTransformerDeployer": "0x80a36559ab9a497fb658325ed771a584eb0f13da",
"exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18",
"mStableBridge": "0x2bf04fcea05f0989a14d9afa37aa376baca6b2b3",
"mooniswapBridge": "0xf384663c04a77a732e7a222c65b33fe471c7011c",
"transformers": {
"wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8",
"payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841",
@ -90,6 +91,7 @@
"exchangeProxyTransformerDeployer": "0x80a36559ab9a497fb658325ed771a584eb0f13da",
"exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18",
"mStableBridge": "0x0000000000000000000000000000000000000000",
"mooniswapBridge": "0x0000000000000000000000000000000000000000",
"transformers": {
"wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8",
"payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841",
@ -139,6 +141,7 @@
"exchangeProxyTransformerDeployer": "0x80a36559ab9a497fb658325ed771a584eb0f13da",
"exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18",
"mStableBridge": "0x0000000000000000000000000000000000000000",
"mooniswapBridge": "0x0000000000000000000000000000000000000000",
"transformers": {
"wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8",
"payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841",
@ -188,6 +191,7 @@
"exchangeProxyTransformerDeployer": "0x80a36559ab9a497fb658325ed771a584eb0f13da",
"exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18",
"mStableBridge": "0x0000000000000000000000000000000000000000",
"mooniswapBridge": "0x0000000000000000000000000000000000000000",
"transformers": {
"wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8",
"payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841",
@ -237,6 +241,7 @@
"exchangeProxyTransformerDeployer": "0x5409ed021d9299bf6814279a6a1411a7e866a631",
"exchangeProxyFlashWallet": "0xb9682a8e7920b431f1d412b8510f0077410c8faa",
"mStableBridge": "0x0000000000000000000000000000000000000000",
"mooniswapBridge": "0x0000000000000000000000000000000000000000",
"transformers": {
"wethTransformer": "0x3f16ca81691dab9184cb4606c361d73c4fd2510a",
"payTakerTransformer": "0x7209185959d7227fb77274e1e88151d7c4c368d3",

View File

@ -42,6 +42,7 @@ export interface ContractAddresses {
exchangeProxyTransformerDeployer: string;
exchangeProxyFlashWallet: string;
mStableBridge: string;
mooniswapBridge: string;
transformers: {
wethTransformer: string;
payTakerTransformer: string;

View File

@ -408,6 +408,7 @@ export async function runMigrationsAsync(
bancorBridge: NULL_ADDRESS,
exchangeProxyGovernor: NULL_ADDRESS,
mStableBridge: NULL_ADDRESS,
mooniswapBridge: NULL_ADDRESS,
exchangeProxy: exchangeProxy.address,
exchangeProxyAllowanceTarget: exchangeProxyAllowanceTargetAddress,
exchangeProxyTransformerDeployer: txDefaults.from,