Mooniswap LP (#143)

* `@0x/asset-swapper`: Fix compiler error on `ILiquidityProvider` call
`@0x/protocol-utils`: Add VIP utils.

* `@0x/asset-swapper`: Clean up curve VIP integration

* `@0x/contracts-zero-ex`: Add `MooniswapLiquidityProvider`.
`@0x/asset-swapper`: Add Mooniswap "vip" to EP quote consumer.

* rebase and prettier

* fix linter error

* `@0x/contracts-zero-ex`: Add `MooniswapLiquidityProvider` tests.

* review feedback

* `@0x/contracts-zero-ex`: Emit `LiquidityProviderFill` events in LPs

* `@0x/asset-swapper`: Fix compilation error

* `@0x/asset-swapper`: Add EP gas overhead to Curve and Mooni LP bridge routes

* `@0x/asset-swapper`: Remove consumer gas overhead for LP VIPs

* `@0x/contracts-zero-ex`: Add more params to `LiquidityProviderFill` event

* `@0x/contracts-zero-ex`: Address review comments.

* `@0x/asset-swapper`: Update deployed Curve and Mooni LPs

Co-authored-by: Lawrence Forman <me@merklejerk.com>
This commit is contained in:
Lawrence Forman
2021-03-02 16:50:37 -05:00
committed by GitHub
parent 61c5e7b948
commit 1a6759820a
16 changed files with 777 additions and 9 deletions

View File

@@ -1,4 +1,17 @@
[
{
"version": "0.20.0",
"changes": [
{
"note": "Add `MooniswapLiquidityProvider`",
"pr": 143
},
{
"note": "Emit `LiquidityProviderFill` event in `CurveLiquidityProvider`",
"pr": 143
}
]
},
{
"version": "0.19.0",
"changes": [

View File

@@ -75,7 +75,8 @@ contract CurveLiquidityProvider is
inputToken,
outputToken,
minBuyAmount,
abi.decode(auxiliaryData, (CurveData))
abi.decode(auxiliaryData, (CurveData)),
recipient
);
// Every pool contract currently checks this but why not.
require(boughtAmount >= minBuyAmount, "CurveLiquidityProvider/UNDERBOUGHT");
@@ -109,7 +110,8 @@ contract CurveLiquidityProvider is
LibERC20Transformer.ETH_TOKEN,
outputToken,
minBuyAmount,
abi.decode(auxiliaryData, (CurveData))
abi.decode(auxiliaryData, (CurveData)),
recipient
);
// Every pool contract currently checks this but why not.
require(boughtAmount >= minBuyAmount, "CurveLiquidityProvider/UNDERBOUGHT");
@@ -141,7 +143,8 @@ contract CurveLiquidityProvider is
inputToken,
LibERC20Transformer.ETH_TOKEN,
minBuyAmount,
abi.decode(auxiliaryData, (CurveData))
abi.decode(auxiliaryData, (CurveData)),
recipient
);
// Every pool contract currently checks this but why not.
require(boughtAmount >= minBuyAmount, "CurveLiquidityProvider/UNDERBOUGHT");
@@ -169,7 +172,8 @@ contract CurveLiquidityProvider is
IERC20TokenV06 inputToken,
IERC20TokenV06 outputToken,
uint256 minBuyAmount,
CurveData memory data
CurveData memory data,
address recipient // Only used to log event.
)
private
returns (uint256 boughtAmount)
@@ -204,5 +208,16 @@ contract CurveLiquidityProvider is
boughtAmount = LibERC20Transformer
.getTokenBalanceOf(outputToken, address(this));
}
emit LiquidityProviderFill(
inputToken,
outputToken,
sellAmount,
boughtAmount,
bytes32("Curve"),
address(data.curveAddress),
msg.sender,
recipient
);
}
}

View File

@@ -0,0 +1,227 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 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-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
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 "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
import "../transformers/LibERC20Transformer.sol";
import "../vendor/ILiquidityProvider.sol";
import "../vendor/IMooniswapPool.sol";
contract MooniswapLiquidityProvider is
ILiquidityProvider
{
using LibERC20TokenV06 for IERC20TokenV06;
using LibSafeMathV06 for uint256;
using LibRichErrorsV06 for bytes;
IEtherTokenV06 private immutable WETH;
constructor(IEtherTokenV06 weth) public {
WETH = weth;
}
/// @dev This contract must be payable because takers can transfer funds
/// in prior to calling the swap function.
receive() external payable {}
/// @dev Trades `inputToken` for `outputToken`. The amount of `inputToken`
/// to sell must be transferred to the contract prior to calling this
/// function to trigger the trade.
/// @param inputToken The token being sold.
/// @param outputToken The token being bought.
/// @param recipient The recipient of the bought tokens.
/// @param minBuyAmount The minimum acceptable amount of `outputToken` to buy.
/// @param auxiliaryData Arbitrary auxiliary data supplied to the contract.
/// @return boughtAmount The amount of `outputToken` bought.
function sellTokenForToken(
IERC20TokenV06 inputToken,
IERC20TokenV06 outputToken,
address recipient,
uint256 minBuyAmount,
bytes calldata auxiliaryData
)
external
override
returns (uint256 boughtAmount)
{
require(
!LibERC20Transformer.isTokenETH(inputToken)
&& !LibERC20Transformer.isTokenETH(outputToken)
&& inputToken != outputToken,
"MooniswapLiquidityProvider/INVALID_ARGS"
);
boughtAmount = _executeSwap(
inputToken,
outputToken,
minBuyAmount,
abi.decode(auxiliaryData, (IMooniswapPool)),
recipient
);
outputToken.compatTransfer(recipient, boughtAmount);
}
/// @dev Trades ETH for token. ETH must either be attached to this function
/// call or sent to the contract prior to calling this function to
/// trigger the trade.
/// @param outputToken The token being bought.
/// @param recipient The recipient of the bought tokens.
/// @param minBuyAmount The minimum acceptable amount of `outputToken` to buy.
/// @param auxiliaryData Arbitrary auxiliary data supplied to the contract.
/// @return boughtAmount The amount of `outputToken` bought.
function sellEthForToken(
IERC20TokenV06 outputToken,
address recipient,
uint256 minBuyAmount,
bytes calldata auxiliaryData
)
external
payable
override
returns (uint256 boughtAmount)
{
require(
!LibERC20Transformer.isTokenETH(outputToken),
"MooniswapLiquidityProvider/INVALID_ARGS"
);
boughtAmount = _executeSwap(
LibERC20Transformer.ETH_TOKEN,
outputToken,
minBuyAmount,
abi.decode(auxiliaryData, (IMooniswapPool)),
recipient
);
outputToken.compatTransfer(recipient, boughtAmount);
}
/// @dev Trades token for ETH. The token must be sent to the contract prior
/// to calling this function to trigger the trade.
/// @param inputToken The token being sold.
/// @param recipient The recipient of the bought tokens.
/// @param minBuyAmount The minimum acceptable amount of ETH to buy.
/// @param auxiliaryData Arbitrary auxiliary data supplied to the contract.
/// @return boughtAmount The amount of ETH bought.
function sellTokenForEth(
IERC20TokenV06 inputToken,
address payable recipient,
uint256 minBuyAmount,
bytes calldata auxiliaryData
)
external
override
returns (uint256 boughtAmount)
{
require(
!LibERC20Transformer.isTokenETH(inputToken),
"MooniswapLiquidityProvider/INVALID_ARGS"
);
boughtAmount = _executeSwap(
inputToken,
LibERC20Transformer.ETH_TOKEN,
minBuyAmount,
abi.decode(auxiliaryData, (IMooniswapPool)),
recipient
);
recipient.call{value: boughtAmount}("");
}
/// @dev Quotes the amount of `outputToken` that would be obtained by
/// selling `sellAmount` of `inputToken`.
function getSellQuote(
IERC20TokenV06 /* inputToken */,
IERC20TokenV06 /* outputToken */,
uint256 /* sellAmount */
)
external
view
override
returns (uint256)
{
revert("MooniswapLiquidityProvider/NOT_IMPLEMENTED");
}
/// @dev Perform the swap against the curve pool. Handles any combination of
/// tokens
function _executeSwap(
IERC20TokenV06 inputToken,
IERC20TokenV06 outputToken,
uint256 minBuyAmount,
IMooniswapPool pool,
address recipient // Only used to log event
)
private
returns (uint256 boughtAmount)
{
uint256 sellAmount =
LibERC20Transformer.getTokenBalanceOf(inputToken, address(this));
uint256 ethValue = 0;
if (inputToken == WETH) {
// Selling WETH. Unwrap to ETH.
require(!_isTokenEthLike(outputToken), 'MooniswapLiquidityProvider/ETH_TO_ETH');
WETH.withdraw(sellAmount);
ethValue = sellAmount;
} else if (LibERC20Transformer.isTokenETH(inputToken)) {
// Selling ETH directly.
ethValue = sellAmount;
require(!_isTokenEthLike(outputToken), 'MooniswapLiquidityProvider/ETH_TO_ETH');
} else {
// Selling a regular ERC20.
require(inputToken != outputToken, 'MooniswapLiquidityProvider/SAME_TOKEN');
inputToken.approveIfBelow(address(pool), sellAmount);
}
boughtAmount = pool.swap{value: ethValue}(
_isTokenEthLike(inputToken) ? IERC20TokenV06(0) : inputToken,
_isTokenEthLike(outputToken) ? IERC20TokenV06(0) : outputToken,
sellAmount,
minBuyAmount,
address(0)
);
if (outputToken == WETH) {
WETH.deposit{value: boughtAmount}();
}
emit LiquidityProviderFill(
inputToken,
outputToken,
sellAmount,
boughtAmount,
bytes32("Mooniswap"),
address(pool),
msg.sender,
recipient
);
}
/// @dev Check if a token is ETH or WETH.
function _isTokenEthLike(IERC20TokenV06 token)
private
view
returns (bool isEthOrWeth)
{
return LibERC20Transformer.isTokenETH(token) || token == WETH;
}
}

View File

@@ -24,6 +24,28 @@ import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
interface ILiquidityProvider {
/// @dev An optional event an LP can emit for each fill against a source.
/// @param inputToken The input token.
/// @param outputToken The output token.
/// @param inputTokenAmount How much input token was sold.
/// @param outputTokenAmount How much output token was bought.
/// @param sourceId A bytes32 encoded ascii source ID. E.g., `bytes32('Curve')`/
/// @param sourceAddress An optional address associated with the source (e.g, a curve pool).
/// @param sourceId A bytes32 encoded ascii source ID. E.g., `bytes32('Curve')`/
/// @param sourceAddress An optional address associated with the source (e.g, a curve pool).
/// @param sender The caller of the LP.
/// @param recipient The recipient of the output tokens.
event LiquidityProviderFill(
IERC20TokenV06 inputToken,
IERC20TokenV06 outputToken,
uint256 inputTokenAmount,
uint256 outputTokenAmount,
bytes32 sourceId,
address sourceAddress,
address sender,
address recipient
);
/// @dev Trades `inputToken` for `outputToken`. The amount of `inputToken`
/// to sell must be transferred to the contract prior to calling this
/// function to trigger the trade.

View File

@@ -0,0 +1,41 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 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";
/// @dev Moooniswap pool interface.
interface IMooniswapPool {
function swap(
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
uint256 minBoughtAmount,
address referrer
)
external
payable
returns (uint256 boughtAmount);
}

View File

@@ -0,0 +1,79 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 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/IERC20TokenV06.sol";
import "./TestMintableERC20Token.sol";
contract TestMooniswap {
event MooniswapCalled(
uint256 value,
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
uint256 minBuyAmount,
address referral
);
uint256 public nextBuyAmount;
function setNextBoughtAmount(uint256 amt)
external
payable
{
nextBuyAmount = amt;
}
function swap(
IERC20TokenV06 sellToken,
TestMintableERC20Token buyToken,
uint256 sellAmount,
uint256 minBuyAmount,
address referral
)
external
payable
returns(uint256 boughtAmount)
{
emit MooniswapCalled(
msg.value,
sellToken,
IERC20TokenV06(address(buyToken)),
sellAmount,
minBuyAmount,
referral
);
boughtAmount = nextBuyAmount;
nextBuyAmount = 0;
require(boughtAmount >= minBuyAmount, 'UNDERBOUGHT');
if (sellToken != IERC20TokenV06(0)) {
sellToken.transferFrom(msg.sender, address(this), sellAmount);
} else {
require(sellAmount == msg.value, 'NOT_ENOUGH_ETH');
}
if (address(buyToken) == address(0)) {
msg.sender.transfer(boughtAmount);
} else {
buyToken.mint(msg.sender, boughtAmount);
}
}
}

View File

@@ -43,7 +43,7 @@
"config": {
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|BridgeSource|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|INativeOrdersFeature|IOwnableFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinDodo|MixinDodoV2|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|NativeOrdersFeature|OwnableFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestNativeOrdersFeature|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx|ZeroExOptimized).json"
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|BridgeSource|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|INativeOrdersFeature|IOwnableFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinDodo|MixinDodoV2|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|MooniswapLiquidityProvider|NativeOrdersFeature|OwnableFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx|ZeroExOptimized).json"
},
"repository": {
"type": "git",

View File

@@ -32,6 +32,7 @@ import * as ILiquidityProvider from '../test/generated-artifacts/ILiquidityProvi
import * as ILiquidityProviderFeature from '../test/generated-artifacts/ILiquidityProviderFeature.json';
import * as ILiquidityProviderSandbox from '../test/generated-artifacts/ILiquidityProviderSandbox.json';
import * as IMetaTransactionsFeature from '../test/generated-artifacts/IMetaTransactionsFeature.json';
import * as IMooniswapPool from '../test/generated-artifacts/IMooniswapPool.json';
import * as INativeOrdersFeature from '../test/generated-artifacts/INativeOrdersFeature.json';
import * as InitialMigration from '../test/generated-artifacts/InitialMigration.json';
import * as IOwnableFeature from '../test/generated-artifacts/IOwnableFeature.json';
@@ -88,6 +89,7 @@ import * as MixinSushiswap from '../test/generated-artifacts/MixinSushiswap.json
import * as MixinUniswap from '../test/generated-artifacts/MixinUniswap.json';
import * as MixinUniswapV2 from '../test/generated-artifacts/MixinUniswapV2.json';
import * as MixinZeroExBridge from '../test/generated-artifacts/MixinZeroExBridge.json';
import * as MooniswapLiquidityProvider from '../test/generated-artifacts/MooniswapLiquidityProvider.json';
import * as NativeOrdersFeature from '../test/generated-artifacts/NativeOrdersFeature.json';
import * as OwnableFeature from '../test/generated-artifacts/OwnableFeature.json';
import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json';
@@ -114,6 +116,7 @@ import * as TestMetaTransactionsTransformERC20Feature from '../test/generated-ar
import * as TestMigrator from '../test/generated-artifacts/TestMigrator.json';
import * as TestMintableERC20Token from '../test/generated-artifacts/TestMintableERC20Token.json';
import * as TestMintTokenERC20Transformer from '../test/generated-artifacts/TestMintTokenERC20Transformer.json';
import * as TestMooniswap from '../test/generated-artifacts/TestMooniswap.json';
import * as TestNativeOrdersFeature from '../test/generated-artifacts/TestNativeOrdersFeature.json';
import * as TestPermissionlessTransformerDeployerSuicidal from '../test/generated-artifacts/TestPermissionlessTransformerDeployerSuicidal.json';
import * as TestPermissionlessTransformerDeployerTransformer from '../test/generated-artifacts/TestPermissionlessTransformerDeployerTransformer.json';
@@ -191,6 +194,7 @@ export const artifacts = {
FixinReentrancyGuard: FixinReentrancyGuard as ContractArtifact,
FixinTokenSpender: FixinTokenSpender as ContractArtifact,
CurveLiquidityProvider: CurveLiquidityProvider as ContractArtifact,
MooniswapLiquidityProvider: MooniswapLiquidityProvider as ContractArtifact,
FullMigration: FullMigration as ContractArtifact,
InitialMigration: InitialMigration as ContractArtifact,
LibBootstrap: LibBootstrap as ContractArtifact,
@@ -233,6 +237,7 @@ export const artifacts = {
MixinUniswapV2: MixinUniswapV2 as ContractArtifact,
MixinZeroExBridge: MixinZeroExBridge as ContractArtifact,
ILiquidityProvider: ILiquidityProvider as ContractArtifact,
IMooniswapPool: IMooniswapPool as ContractArtifact,
IERC20Bridge: IERC20Bridge as ContractArtifact,
IStaking: IStaking as ContractArtifact,
ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact,
@@ -256,6 +261,7 @@ export const artifacts = {
TestMigrator: TestMigrator as ContractArtifact,
TestMintTokenERC20Transformer: TestMintTokenERC20Transformer as ContractArtifact,
TestMintableERC20Token: TestMintableERC20Token as ContractArtifact,
TestMooniswap: TestMooniswap as ContractArtifact,
TestNativeOrdersFeature: TestNativeOrdersFeature as ContractArtifact,
TestPermissionlessTransformerDeployerSuicidal: TestPermissionlessTransformerDeployerSuicidal as ContractArtifact,
TestPermissionlessTransformerDeployerTransformer: TestPermissionlessTransformerDeployerTransformer as ContractArtifact,

View File

@@ -291,4 +291,32 @@ blockchainTests.resets('CurveLiquidityProvider feature', env => {
const call = lp.sellEthForToken(ETH_TOKEN_ADDRESS, RECIPIENT, BUY_AMOUNT, encodeCurveData());
return expect(call.callAsync()).to.revertWith('CurveLiquidityProvider/INVALID_ARGS');
});
it('emits a LiquidityProviderFill event', async () => {
await fundProviderContractAsync(SELL_TOKEN_COIN_IDX);
const call = lp.sellTokenForToken(
sellToken.address,
buyToken.address,
RECIPIENT,
BUY_AMOUNT,
encodeCurveData(),
);
const { logs } = await call.awaitTransactionSuccessAsync();
verifyEventsFromLogs(
logs,
[
{
inputToken: sellToken.address,
outputToken: buyToken.address,
inputTokenAmount: SELL_AMOUNT,
outputTokenAmount: BUY_AMOUNT,
sourceId: hexUtils.rightPad(hexUtils.toHex(Buffer.from('Curve'))),
sourceAddress: testCurve.address,
sender: taker,
recipient: RECIPIENT,
},
],
'LiquidityProviderFill',
);
});
});

View File

@@ -0,0 +1,289 @@
import { blockchainTests, constants, expect, getRandomInteger, verifyEventsFromLogs } from '@0x/contracts-test-utils';
import { BigNumber, hexUtils } from '@0x/utils';
import { artifacts } from '../artifacts';
import {
MooniswapLiquidityProviderContract,
TestMintableERC20TokenContract,
TestMooniswapContract,
TestWethContract,
} from '../wrappers';
blockchainTests.resets('MooniswapLiquidityProvider feature', env => {
let lp: MooniswapLiquidityProviderContract;
let sellToken: TestMintableERC20TokenContract;
let buyToken: TestMintableERC20TokenContract;
let weth: TestWethContract;
let testMooniswap: TestMooniswapContract;
let owner: string;
let taker: string;
let mooniswapData: string;
const RECIPIENT = hexUtils.random(20);
const ETH_TOKEN_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
const { NULL_ADDRESS, ZERO_AMOUNT } = constants;
const SELL_AMOUNT = getRandomInteger('1e18', '10e18');
const BUY_AMOUNT = getRandomInteger('1e18', '10e18');
before(async () => {
[owner, taker] = await env.getAccountAddressesAsync();
[sellToken, buyToken] = await Promise.all(
new Array(2)
.fill(0)
.map(async () =>
TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
artifacts.TestMintableERC20Token,
env.provider,
env.txDefaults,
artifacts,
),
),
);
weth = await TestWethContract.deployFrom0xArtifactAsync(
artifacts.TestWeth,
env.provider,
env.txDefaults,
artifacts,
);
testMooniswap = await TestMooniswapContract.deployFrom0xArtifactAsync(
artifacts.TestMooniswap,
env.provider,
{ ...env.txDefaults },
artifacts,
);
lp = await MooniswapLiquidityProviderContract.deployFrom0xArtifactAsync(
artifacts.MooniswapLiquidityProvider,
env.provider,
{ ...env.txDefaults, from: taker },
artifacts,
weth.address,
);
mooniswapData = hexUtils.leftPad(testMooniswap.address);
});
async function prepareNextSwapFundsAsync(
sellTokenAddress: string,
sellAmount: BigNumber,
buyTokenAddress: string,
buyAmount: BigNumber,
): Promise<void> {
if (sellTokenAddress.toLowerCase() === weth.address.toLowerCase()) {
await weth.deposit().awaitTransactionSuccessAsync({
from: taker,
value: sellAmount,
});
await weth.transfer(lp.address, sellAmount).awaitTransactionSuccessAsync({ from: taker });
} else if (sellTokenAddress.toLowerCase() === sellToken.address.toLowerCase()) {
await sellToken.mint(lp.address, sellAmount).awaitTransactionSuccessAsync();
} else {
await await env.web3Wrapper.awaitTransactionSuccessAsync(
await env.web3Wrapper.sendTransactionAsync({
to: lp.address,
from: taker,
value: sellAmount,
}),
);
}
await testMooniswap.setNextBoughtAmount(buyAmount).awaitTransactionSuccessAsync({
value: buyTokenAddress.toLowerCase() === ETH_TOKEN_ADDRESS.toLowerCase() ? buyAmount : ZERO_AMOUNT,
});
}
it('can swap ERC20->ERC20', async () => {
await prepareNextSwapFundsAsync(sellToken.address, SELL_AMOUNT, buyToken.address, BUY_AMOUNT);
const call = lp.sellTokenForToken(sellToken.address, buyToken.address, RECIPIENT, BUY_AMOUNT, mooniswapData);
const boughtAmount = await call.callAsync();
const { logs } = await call.awaitTransactionSuccessAsync();
expect(boughtAmount).to.bignumber.eq(BUY_AMOUNT);
expect(await buyToken.balanceOf(RECIPIENT).callAsync()).to.bignumber.eq(BUY_AMOUNT);
verifyEventsFromLogs(
logs,
[
{
value: ZERO_AMOUNT,
sellToken: sellToken.address,
buyToken: buyToken.address,
sellAmount: SELL_AMOUNT,
minBuyAmount: BUY_AMOUNT,
referral: NULL_ADDRESS,
},
],
'MooniswapCalled',
);
});
it('can swap ERC20->ETH', async () => {
await prepareNextSwapFundsAsync(sellToken.address, SELL_AMOUNT, ETH_TOKEN_ADDRESS, BUY_AMOUNT);
const call = lp.sellTokenForEth(sellToken.address, RECIPIENT, BUY_AMOUNT, mooniswapData);
const boughtAmount = await call.callAsync();
const { logs } = await call.awaitTransactionSuccessAsync();
expect(boughtAmount).to.bignumber.eq(BUY_AMOUNT);
expect(await env.web3Wrapper.getBalanceInWeiAsync(RECIPIENT)).to.bignumber.eq(BUY_AMOUNT);
verifyEventsFromLogs(
logs,
[
{
value: ZERO_AMOUNT,
sellToken: sellToken.address,
buyToken: NULL_ADDRESS,
sellAmount: SELL_AMOUNT,
minBuyAmount: BUY_AMOUNT,
referral: NULL_ADDRESS,
},
],
'MooniswapCalled',
);
});
it('can swap ETH->ERC20', async () => {
await prepareNextSwapFundsAsync(ETH_TOKEN_ADDRESS, SELL_AMOUNT, buyToken.address, BUY_AMOUNT);
const call = lp.sellEthForToken(buyToken.address, RECIPIENT, BUY_AMOUNT, mooniswapData);
const boughtAmount = await call.callAsync();
const { logs } = await call.awaitTransactionSuccessAsync();
expect(boughtAmount).to.bignumber.eq(BUY_AMOUNT);
expect(await buyToken.balanceOf(RECIPIENT).callAsync()).to.bignumber.eq(BUY_AMOUNT);
verifyEventsFromLogs(
logs,
[
{
value: SELL_AMOUNT,
sellToken: NULL_ADDRESS,
buyToken: buyToken.address,
sellAmount: SELL_AMOUNT,
minBuyAmount: BUY_AMOUNT,
referral: NULL_ADDRESS,
},
],
'MooniswapCalled',
);
});
it('can swap ETH->ERC20 with attached ETH', async () => {
await testMooniswap.setNextBoughtAmount(BUY_AMOUNT).awaitTransactionSuccessAsync();
const call = lp.sellEthForToken(buyToken.address, RECIPIENT, BUY_AMOUNT, mooniswapData);
const boughtAmount = await call.callAsync({ value: SELL_AMOUNT });
const { logs } = await call.awaitTransactionSuccessAsync({ value: SELL_AMOUNT });
expect(boughtAmount).to.bignumber.eq(BUY_AMOUNT);
expect(await buyToken.balanceOf(RECIPIENT).callAsync()).to.bignumber.eq(BUY_AMOUNT);
verifyEventsFromLogs(
logs,
[
{
value: SELL_AMOUNT,
sellToken: NULL_ADDRESS,
buyToken: buyToken.address,
sellAmount: SELL_AMOUNT,
minBuyAmount: BUY_AMOUNT,
referral: NULL_ADDRESS,
},
],
'MooniswapCalled',
);
});
it('can swap ERC20->WETH', async () => {
await prepareNextSwapFundsAsync(
sellToken.address,
SELL_AMOUNT,
ETH_TOKEN_ADDRESS, // Mooni contract holds ETH.
BUY_AMOUNT,
);
const call = lp.sellTokenForToken(sellToken.address, weth.address, RECIPIENT, BUY_AMOUNT, mooniswapData);
const boughtAmount = await call.callAsync();
const { logs } = await call.awaitTransactionSuccessAsync();
expect(boughtAmount).to.bignumber.eq(BUY_AMOUNT);
expect(await sellToken.balanceOf(testMooniswap.address).callAsync()).to.bignumber.eq(SELL_AMOUNT);
expect(await weth.balanceOf(RECIPIENT).callAsync()).to.bignumber.eq(BUY_AMOUNT);
verifyEventsFromLogs(
logs,
[
{
value: ZERO_AMOUNT,
sellToken: sellToken.address,
buyToken: NULL_ADDRESS,
sellAmount: SELL_AMOUNT,
minBuyAmount: BUY_AMOUNT,
referral: NULL_ADDRESS,
},
],
'MooniswapCalled',
);
});
it('can swap WETH->ERC20', async () => {
await prepareNextSwapFundsAsync(weth.address, SELL_AMOUNT, buyToken.address, BUY_AMOUNT);
const call = lp.sellTokenForToken(weth.address, buyToken.address, RECIPIENT, BUY_AMOUNT, mooniswapData);
const boughtAmount = await call.callAsync();
const { logs } = await call.awaitTransactionSuccessAsync();
expect(boughtAmount).to.bignumber.eq(BUY_AMOUNT);
expect(await env.web3Wrapper.getBalanceInWeiAsync(testMooniswap.address)).to.bignumber.eq(SELL_AMOUNT);
expect(await buyToken.balanceOf(RECIPIENT).callAsync()).to.bignumber.eq(BUY_AMOUNT);
verifyEventsFromLogs(
logs,
[
{
value: SELL_AMOUNT,
sellToken: NULL_ADDRESS,
buyToken: buyToken.address,
sellAmount: SELL_AMOUNT,
minBuyAmount: BUY_AMOUNT,
referral: NULL_ADDRESS,
},
],
'MooniswapCalled',
);
});
it('reverts if pool reverts', async () => {
await prepareNextSwapFundsAsync(sellToken.address, SELL_AMOUNT, buyToken.address, BUY_AMOUNT);
await testMooniswap.setNextBoughtAmount(BUY_AMOUNT.minus(1)).awaitTransactionSuccessAsync();
const call = lp.sellTokenForToken(sellToken.address, buyToken.address, RECIPIENT, BUY_AMOUNT, mooniswapData);
return expect(call.callAsync()).to.revertWith('UNDERBOUGHT');
});
it('reverts if ERC20->ERC20 is the same token', async () => {
const call = lp.sellTokenForToken(sellToken.address, sellToken.address, RECIPIENT, BUY_AMOUNT, mooniswapData);
return expect(call.callAsync()).to.revertWith('MooniswapLiquidityProvider/INVALID_ARGS');
});
it('reverts if ERC20->ERC20 receives an ETH input token', async () => {
const call = lp.sellTokenForToken(ETH_TOKEN_ADDRESS, buyToken.address, RECIPIENT, BUY_AMOUNT, mooniswapData);
return expect(call.callAsync()).to.revertWith('MooniswapLiquidityProvider/INVALID_ARGS');
});
it('reverts if ERC20->ERC20 receives an ETH output token', async () => {
const call = lp.sellTokenForToken(sellToken.address, ETH_TOKEN_ADDRESS, RECIPIENT, BUY_AMOUNT, mooniswapData);
return expect(call.callAsync()).to.revertWith('MooniswapLiquidityProvider/INVALID_ARGS');
});
it('reverts if ERC20->ETH receives an ETH input token', async () => {
const call = lp.sellTokenForEth(ETH_TOKEN_ADDRESS, RECIPIENT, BUY_AMOUNT, mooniswapData);
return expect(call.callAsync()).to.revertWith('MooniswapLiquidityProvider/INVALID_ARGS');
});
it('reverts if ETH->ERC20 receives an ETH output token', async () => {
const call = lp.sellEthForToken(ETH_TOKEN_ADDRESS, RECIPIENT, BUY_AMOUNT, mooniswapData);
return expect(call.callAsync()).to.revertWith('MooniswapLiquidityProvider/INVALID_ARGS');
});
it('emits a LiquidityProviderFill event', async () => {
await prepareNextSwapFundsAsync(sellToken.address, SELL_AMOUNT, buyToken.address, BUY_AMOUNT);
const call = lp.sellTokenForToken(sellToken.address, buyToken.address, RECIPIENT, BUY_AMOUNT, mooniswapData);
const { logs } = await call.awaitTransactionSuccessAsync();
verifyEventsFromLogs(
logs,
[
{
inputToken: sellToken.address,
outputToken: buyToken.address,
inputTokenAmount: SELL_AMOUNT,
outputTokenAmount: BUY_AMOUNT,
sourceId: hexUtils.rightPad(hexUtils.toHex(Buffer.from('Mooniswap'))),
sourceAddress: testMooniswap.address,
sender: taker,
recipient: RECIPIENT,
},
],
'LiquidityProviderFill',
);
});
});

View File

@@ -30,6 +30,7 @@ export * from '../test/generated-wrappers/i_liquidity_provider';
export * from '../test/generated-wrappers/i_liquidity_provider_feature';
export * from '../test/generated-wrappers/i_liquidity_provider_sandbox';
export * from '../test/generated-wrappers/i_meta_transactions_feature';
export * from '../test/generated-wrappers/i_mooniswap_pool';
export * from '../test/generated-wrappers/i_native_orders_feature';
export * from '../test/generated-wrappers/i_ownable_feature';
export * from '../test/generated-wrappers/i_simple_function_registry_feature';
@@ -86,6 +87,7 @@ export * from '../test/generated-wrappers/mixin_sushiswap';
export * from '../test/generated-wrappers/mixin_uniswap';
export * from '../test/generated-wrappers/mixin_uniswap_v2';
export * from '../test/generated-wrappers/mixin_zero_ex_bridge';
export * from '../test/generated-wrappers/mooniswap_liquidity_provider';
export * from '../test/generated-wrappers/native_orders_feature';
export * from '../test/generated-wrappers/ownable_feature';
export * from '../test/generated-wrappers/pay_taker_transformer';
@@ -112,6 +114,7 @@ export * from '../test/generated-wrappers/test_meta_transactions_transform_erc20
export * from '../test/generated-wrappers/test_migrator';
export * from '../test/generated-wrappers/test_mint_token_erc20_transformer';
export * from '../test/generated-wrappers/test_mintable_erc20_token';
export * from '../test/generated-wrappers/test_mooniswap';
export * from '../test/generated-wrappers/test_native_orders_feature';
export * from '../test/generated-wrappers/test_permissionless_transformer_deployer_suicidal';
export * from '../test/generated-wrappers/test_permissionless_transformer_deployer_transformer';

View File

@@ -60,6 +60,7 @@
"test/generated-artifacts/ILiquidityProviderFeature.json",
"test/generated-artifacts/ILiquidityProviderSandbox.json",
"test/generated-artifacts/IMetaTransactionsFeature.json",
"test/generated-artifacts/IMooniswapPool.json",
"test/generated-artifacts/INativeOrdersFeature.json",
"test/generated-artifacts/IOwnableFeature.json",
"test/generated-artifacts/ISimpleFunctionRegistryFeature.json",
@@ -116,6 +117,7 @@
"test/generated-artifacts/MixinUniswap.json",
"test/generated-artifacts/MixinUniswapV2.json",
"test/generated-artifacts/MixinZeroExBridge.json",
"test/generated-artifacts/MooniswapLiquidityProvider.json",
"test/generated-artifacts/NativeOrdersFeature.json",
"test/generated-artifacts/OwnableFeature.json",
"test/generated-artifacts/PayTakerTransformer.json",
@@ -142,6 +144,7 @@
"test/generated-artifacts/TestMigrator.json",
"test/generated-artifacts/TestMintTokenERC20Transformer.json",
"test/generated-artifacts/TestMintableERC20Token.json",
"test/generated-artifacts/TestMooniswap.json",
"test/generated-artifacts/TestNativeOrdersFeature.json",
"test/generated-artifacts/TestPermissionlessTransformerDeployerSuicidal.json",
"test/generated-artifacts/TestPermissionlessTransformerDeployerTransformer.json",