@0x/contracts-zero-ex
: Add CurveLiquidityProvider
and misc refactors (#127)
* `@0x/contracts-zero-ex`: Add `CurveLiquidityProvider` and misc refactors * `@0x/asset-swapper`: Fix compiler error on `ILiquidityProvider` call `@0x/protocol-utils`: Add VIP utils. * `@0x/contracts-zero-ex`: Rebase and fix comiler warnings * `@0x/asset-swapper`: Clean up curve VIP integration * `@0x/protocol-utils`: Update changelog * `@0x/protocol-utils`: tsdoc new functions Co-authored-by: Lawrence Forman <me@merklejerk.com>
This commit is contained in:
parent
72c5399b9d
commit
a74a3450eb
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "0.19.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add `CurveLiquidityProvider` and misc refactors",
|
||||
"pr": 127
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "0.18.2",
|
||||
"changes": [
|
||||
|
@ -20,6 +20,9 @@
|
||||
pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "../vendor/ILiquidityProvider.sol";
|
||||
|
||||
|
||||
interface ILiquidityProviderSandbox {
|
||||
|
||||
@ -32,9 +35,9 @@ interface ILiquidityProviderSandbox {
|
||||
/// @param minBuyAmount The minimum acceptable amount of `outputToken` to buy.
|
||||
/// @param auxiliaryData Auxiliary data supplied to the `provider` contract.
|
||||
function executeSellTokenForToken(
|
||||
address provider,
|
||||
address inputToken,
|
||||
address outputToken,
|
||||
ILiquidityProvider provider,
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
address recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
@ -49,8 +52,8 @@ interface ILiquidityProviderSandbox {
|
||||
/// @param minBuyAmount The minimum acceptable amount of `outputToken` to buy.
|
||||
/// @param auxiliaryData Auxiliary data supplied to the `provider` contract.
|
||||
function executeSellEthForToken(
|
||||
address provider,
|
||||
address outputToken,
|
||||
ILiquidityProvider provider,
|
||||
IERC20TokenV06 outputToken,
|
||||
address recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
@ -65,8 +68,8 @@ interface ILiquidityProviderSandbox {
|
||||
/// @param minBuyAmount The minimum acceptable amount of ETH to buy.
|
||||
/// @param auxiliaryData Auxiliary data supplied to the `provider` contract.
|
||||
function executeSellTokenForEth(
|
||||
address provider,
|
||||
address inputToken,
|
||||
ILiquidityProvider provider,
|
||||
IERC20TokenV06 inputToken,
|
||||
address recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
|
@ -17,6 +17,7 @@ pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibOwnableRichErrorsV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "../vendor/ILiquidityProvider.sol";
|
||||
import "../vendor/v3/IERC20Bridge.sol";
|
||||
import "./ILiquidityProviderSandbox.sol";
|
||||
@ -58,9 +59,9 @@ contract LiquidityProviderSandbox is
|
||||
/// @param minBuyAmount The minimum acceptable amount of `outputToken` to buy.
|
||||
/// @param auxiliaryData Auxiliary data supplied to the `provider` contract.
|
||||
function executeSellTokenForToken(
|
||||
address provider,
|
||||
address inputToken,
|
||||
address outputToken,
|
||||
ILiquidityProvider provider,
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
address recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
@ -69,7 +70,7 @@ contract LiquidityProviderSandbox is
|
||||
onlyOwner
|
||||
override
|
||||
{
|
||||
ILiquidityProvider(provider).sellTokenForToken(
|
||||
provider.sellTokenForToken(
|
||||
inputToken,
|
||||
outputToken,
|
||||
recipient,
|
||||
@ -86,8 +87,8 @@ contract LiquidityProviderSandbox is
|
||||
/// @param minBuyAmount The minimum acceptable amount of `outputToken` to buy.
|
||||
/// @param auxiliaryData Auxiliary data supplied to the `provider` contract.
|
||||
function executeSellEthForToken(
|
||||
address provider,
|
||||
address outputToken,
|
||||
ILiquidityProvider provider,
|
||||
IERC20TokenV06 outputToken,
|
||||
address recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
@ -96,7 +97,7 @@ contract LiquidityProviderSandbox is
|
||||
onlyOwner
|
||||
override
|
||||
{
|
||||
ILiquidityProvider(provider).sellEthForToken(
|
||||
provider.sellEthForToken(
|
||||
outputToken,
|
||||
recipient,
|
||||
minBuyAmount,
|
||||
@ -112,8 +113,8 @@ contract LiquidityProviderSandbox is
|
||||
/// @param minBuyAmount The minimum acceptable amount of ETH to buy.
|
||||
/// @param auxiliaryData Auxiliary data supplied to the `provider` contract.
|
||||
function executeSellTokenForEth(
|
||||
address provider,
|
||||
address inputToken,
|
||||
ILiquidityProvider provider,
|
||||
IERC20TokenV06 inputToken,
|
||||
address recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
@ -122,7 +123,7 @@ contract LiquidityProviderSandbox is
|
||||
onlyOwner
|
||||
override
|
||||
{
|
||||
ILiquidityProvider(provider).sellTokenForEth(
|
||||
provider.sellTokenForEth(
|
||||
inputToken,
|
||||
payable(recipient),
|
||||
minBuyAmount,
|
||||
|
@ -20,10 +20,23 @@
|
||||
pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "../vendor/ILiquidityProvider.sol";
|
||||
|
||||
|
||||
/// @dev Feature to swap directly with an on-chain liquidity provider.
|
||||
interface ILiquidityProviderFeature {
|
||||
|
||||
/// @dev Event for data pipeline.
|
||||
event LiquidityProviderSwap(
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
uint256 inputTokenAmount,
|
||||
uint256 outputTokenAmount,
|
||||
ILiquidityProvider provider,
|
||||
address recipient
|
||||
);
|
||||
|
||||
/// @dev Sells `sellAmount` of `inputToken` to the liquidity provider
|
||||
/// at the given `provider` address.
|
||||
/// @param inputToken The token being sold.
|
||||
@ -38,9 +51,9 @@ interface ILiquidityProviderFeature {
|
||||
/// @param auxiliaryData Auxiliary data supplied to the `provider` contract.
|
||||
/// @return boughtAmount The amount of `outputToken` bought.
|
||||
function sellToLiquidityProvider(
|
||||
address inputToken,
|
||||
address outputToken,
|
||||
address payable provider,
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
ILiquidityProvider provider,
|
||||
address recipient,
|
||||
uint256 sellAmount,
|
||||
uint256 minBuyAmount,
|
||||
|
@ -23,12 +23,14 @@ pragma experimental ABIEncoderV2;
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "../errors/LibLiquidityProviderRichErrors.sol";
|
||||
import "../external/ILiquidityProviderSandbox.sol";
|
||||
import "../external/LiquidityProviderSandbox.sol";
|
||||
import "../fixins/FixinCommon.sol";
|
||||
import "../fixins/FixinTokenSpender.sol";
|
||||
import "../migrations/LibMigrate.sol";
|
||||
import "../transformers/LibERC20Transformer.sol";
|
||||
import "./IFeature.sol";
|
||||
import "./ILiquidityProviderFeature.sol";
|
||||
|
||||
@ -45,23 +47,11 @@ contract LiquidityProviderFeature is
|
||||
/// @dev Name of this feature.
|
||||
string public constant override FEATURE_NAME = "LiquidityProviderFeature";
|
||||
/// @dev Version of this feature.
|
||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 2);
|
||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 3);
|
||||
|
||||
/// @dev ETH pseudo-token address.
|
||||
address constant internal ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
|
||||
/// @dev The sandbox contract address.
|
||||
ILiquidityProviderSandbox public immutable sandbox;
|
||||
|
||||
/// @dev Event for data pipeline.
|
||||
event LiquidityProviderSwap(
|
||||
address inputToken,
|
||||
address outputToken,
|
||||
uint256 inputTokenAmount,
|
||||
uint256 outputTokenAmount,
|
||||
address provider,
|
||||
address recipient
|
||||
);
|
||||
|
||||
constructor(LiquidityProviderSandbox sandbox_, bytes32 greedyTokensBloomFilter)
|
||||
public
|
||||
FixinCommon()
|
||||
@ -95,9 +85,9 @@ contract LiquidityProviderFeature is
|
||||
/// @param auxiliaryData Auxiliary data supplied to the `provider` contract.
|
||||
/// @return boughtAmount The amount of `outputToken` bought.
|
||||
function sellToLiquidityProvider(
|
||||
address inputToken,
|
||||
address outputToken,
|
||||
address payable provider,
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
ILiquidityProvider provider,
|
||||
address recipient,
|
||||
uint256 sellAmount,
|
||||
uint256 minBuyAmount,
|
||||
@ -114,21 +104,21 @@ contract LiquidityProviderFeature is
|
||||
|
||||
// Forward all attached ETH to the provider.
|
||||
if (msg.value > 0) {
|
||||
provider.transfer(msg.value);
|
||||
payable(address(provider)).transfer(msg.value);
|
||||
}
|
||||
|
||||
if (inputToken != ETH_TOKEN_ADDRESS) {
|
||||
if (!LibERC20Transformer.isTokenETH(inputToken)) {
|
||||
// Transfer input ERC20 tokens to the provider.
|
||||
_transferERC20Tokens(
|
||||
IERC20TokenV06(inputToken),
|
||||
inputToken,
|
||||
msg.sender,
|
||||
provider,
|
||||
address(provider),
|
||||
sellAmount
|
||||
);
|
||||
}
|
||||
|
||||
if (inputToken == ETH_TOKEN_ADDRESS) {
|
||||
uint256 balanceBefore = IERC20TokenV06(outputToken).balanceOf(recipient);
|
||||
if (LibERC20Transformer.isTokenETH(inputToken)) {
|
||||
uint256 balanceBefore = outputToken.balanceOf(recipient);
|
||||
sandbox.executeSellEthForToken(
|
||||
provider,
|
||||
outputToken,
|
||||
@ -137,7 +127,7 @@ contract LiquidityProviderFeature is
|
||||
auxiliaryData
|
||||
);
|
||||
boughtAmount = IERC20TokenV06(outputToken).balanceOf(recipient).safeSub(balanceBefore);
|
||||
} else if (outputToken == ETH_TOKEN_ADDRESS) {
|
||||
} else if (LibERC20Transformer.isTokenETH(outputToken)) {
|
||||
uint256 balanceBefore = recipient.balance;
|
||||
sandbox.executeSellTokenForEth(
|
||||
provider,
|
||||
@ -148,7 +138,7 @@ contract LiquidityProviderFeature is
|
||||
);
|
||||
boughtAmount = recipient.balance.safeSub(balanceBefore);
|
||||
} else {
|
||||
uint256 balanceBefore = IERC20TokenV06(outputToken).balanceOf(recipient);
|
||||
uint256 balanceBefore = outputToken.balanceOf(recipient);
|
||||
sandbox.executeSellTokenForToken(
|
||||
provider,
|
||||
inputToken,
|
||||
@ -157,14 +147,14 @@ contract LiquidityProviderFeature is
|
||||
minBuyAmount,
|
||||
auxiliaryData
|
||||
);
|
||||
boughtAmount = IERC20TokenV06(outputToken).balanceOf(recipient).safeSub(balanceBefore);
|
||||
boughtAmount = outputToken.balanceOf(recipient).safeSub(balanceBefore);
|
||||
}
|
||||
|
||||
if (boughtAmount < minBuyAmount) {
|
||||
LibLiquidityProviderRichErrors.LiquidityProviderIncompleteSellError(
|
||||
provider,
|
||||
outputToken,
|
||||
inputToken,
|
||||
address(provider),
|
||||
address(outputToken),
|
||||
address(inputToken),
|
||||
sellAmount,
|
||||
boughtAmount,
|
||||
minBuyAmount
|
||||
|
@ -0,0 +1,208 @@
|
||||
// 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-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
import "../transformers/LibERC20Transformer.sol";
|
||||
import "../vendor/ILiquidityProvider.sol";
|
||||
|
||||
|
||||
contract CurveLiquidityProvider is
|
||||
ILiquidityProvider
|
||||
{
|
||||
using LibERC20TokenV06 for IERC20TokenV06;
|
||||
using LibSafeMathV06 for uint256;
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
struct CurveData {
|
||||
address curveAddress;
|
||||
bytes4 exchangeFunctionSelector;
|
||||
int128 fromCoinIdx;
|
||||
int128 toCoinIdx;
|
||||
}
|
||||
|
||||
/// @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),
|
||||
"CurveLiquidityProvider/INVALID_ARGS"
|
||||
);
|
||||
boughtAmount = _executeSwap(
|
||||
inputToken,
|
||||
outputToken,
|
||||
minBuyAmount,
|
||||
abi.decode(auxiliaryData, (CurveData))
|
||||
);
|
||||
// Every pool contract currently checks this but why not.
|
||||
require(boughtAmount >= minBuyAmount, "CurveLiquidityProvider/UNDERBOUGHT");
|
||||
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),
|
||||
"CurveLiquidityProvider/INVALID_ARGS"
|
||||
);
|
||||
boughtAmount = _executeSwap(
|
||||
LibERC20Transformer.ETH_TOKEN,
|
||||
outputToken,
|
||||
minBuyAmount,
|
||||
abi.decode(auxiliaryData, (CurveData))
|
||||
);
|
||||
// Every pool contract currently checks this but why not.
|
||||
require(boughtAmount >= minBuyAmount, "CurveLiquidityProvider/UNDERBOUGHT");
|
||||
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),
|
||||
"CurveLiquidityProvider/INVALID_ARGS"
|
||||
);
|
||||
boughtAmount = _executeSwap(
|
||||
inputToken,
|
||||
LibERC20Transformer.ETH_TOKEN,
|
||||
minBuyAmount,
|
||||
abi.decode(auxiliaryData, (CurveData))
|
||||
);
|
||||
// Every pool contract currently checks this but why not.
|
||||
require(boughtAmount >= minBuyAmount, "CurveLiquidityProvider/UNDERBOUGHT");
|
||||
recipient.transfer(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("CurveLiquidityProvider/NOT_IMPLEMENTED");
|
||||
}
|
||||
|
||||
/// @dev Perform the swap against the curve pool. Handles any combination of
|
||||
/// tokens
|
||||
function _executeSwap(
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
uint256 minBuyAmount,
|
||||
CurveData memory data
|
||||
)
|
||||
private
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
uint256 sellAmount =
|
||||
LibERC20Transformer.getTokenBalanceOf(inputToken, address(this));
|
||||
if (!LibERC20Transformer.isTokenETH(inputToken)) {
|
||||
inputToken.approveIfBelow(data.curveAddress, sellAmount);
|
||||
}
|
||||
|
||||
(bool success, bytes memory resultData) =
|
||||
data.curveAddress.call
|
||||
{ value: LibERC20Transformer.isTokenETH(inputToken) ? sellAmount : 0 }
|
||||
(abi.encodeWithSelector(
|
||||
data.exchangeFunctionSelector,
|
||||
data.fromCoinIdx,
|
||||
data.toCoinIdx,
|
||||
// dx
|
||||
sellAmount,
|
||||
// min dy
|
||||
minBuyAmount
|
||||
));
|
||||
if (!success) {
|
||||
resultData.rrevert();
|
||||
}
|
||||
if (resultData.length == 32) {
|
||||
// Pool returned a boughtAmount
|
||||
boughtAmount = abi.decode(resultData, (uint256));
|
||||
} else {
|
||||
// Not all pool contracts return a `boughtAmount`, so we return
|
||||
// our balance of the output token if it wasn't returned.
|
||||
boughtAmount = LibERC20Transformer
|
||||
.getTokenBalanceOf(outputToken, address(this));
|
||||
}
|
||||
}
|
||||
}
|
@ -47,8 +47,8 @@ contract MixinZeroExBridge {
|
||||
sellAmount
|
||||
);
|
||||
boughtAmount = provider.sellTokenForToken(
|
||||
address(sellToken),
|
||||
address(buyToken),
|
||||
sellToken,
|
||||
buyToken,
|
||||
address(this), // recipient
|
||||
1, // minBuyAmount
|
||||
lpData
|
||||
|
@ -19,6 +19,9 @@
|
||||
|
||||
pragma solidity ^0.6.5;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
|
||||
|
||||
interface ILiquidityProvider {
|
||||
|
||||
/// @dev Trades `inputToken` for `outputToken`. The amount of `inputToken`
|
||||
@ -31,8 +34,8 @@ interface ILiquidityProvider {
|
||||
/// @param auxiliaryData Arbitrary auxiliary data supplied to the contract.
|
||||
/// @return boughtAmount The amount of `outputToken` bought.
|
||||
function sellTokenForToken(
|
||||
address inputToken,
|
||||
address outputToken,
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
address recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
@ -49,7 +52,7 @@ interface ILiquidityProvider {
|
||||
/// @param auxiliaryData Arbitrary auxiliary data supplied to the contract.
|
||||
/// @return boughtAmount The amount of `outputToken` bought.
|
||||
function sellEthForToken(
|
||||
address outputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
address recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
@ -66,7 +69,7 @@ interface ILiquidityProvider {
|
||||
/// @param auxiliaryData Arbitrary auxiliary data supplied to the contract.
|
||||
/// @return boughtAmount The amount of ETH bought.
|
||||
function sellTokenForEth(
|
||||
address inputToken,
|
||||
IERC20TokenV06 inputToken,
|
||||
address payable recipient,
|
||||
uint256 minBuyAmount,
|
||||
bytes calldata auxiliaryData
|
||||
@ -83,8 +86,8 @@ interface ILiquidityProvider {
|
||||
/// @param sellAmount Amount of `inputToken` to sell.
|
||||
/// @return outputTokenAmount Amount of `outputToken` that would be obtained.
|
||||
function getSellQuote(
|
||||
address inputToken,
|
||||
address outputToken,
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
uint256 sellAmount
|
||||
)
|
||||
external
|
||||
|
100
contracts/zero-ex/contracts/test/TestCurve.sol
Normal file
100
contracts/zero-ex/contracts/test/TestCurve.sol
Normal file
@ -0,0 +1,100 @@
|
||||
// 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 TestCurve {
|
||||
|
||||
event CurveCalled(
|
||||
uint256 value,
|
||||
bytes4 selector,
|
||||
int128 fromCoinIdx,
|
||||
int128 toCoinIdx,
|
||||
uint256 sellAmount,
|
||||
uint256 minBuyAmount
|
||||
);
|
||||
|
||||
// The lower 16 bits of the selector are reserved for flags.
|
||||
bytes4 public constant BASE_SWAP_SELECTOR = 0x12340000;
|
||||
bytes4 public constant RETURN_BOUGHT_AMOUNT_SELECTOR_FLAG = 0x00000001;
|
||||
|
||||
int128 public constant SELL_TOKEN_COIN_IDX = 0;
|
||||
int128 public constant BUY_TOKEN_COIN_IDX = 1;
|
||||
int128 public constant ETH_COIN_IDX = 2;
|
||||
|
||||
uint256 public buyAmount;
|
||||
IERC20TokenV06 public sellToken;
|
||||
TestMintableERC20Token public buyToken;
|
||||
|
||||
constructor(
|
||||
IERC20TokenV06 sellToken_,
|
||||
TestMintableERC20Token buyToken_,
|
||||
uint256 buyAmount_
|
||||
)
|
||||
public
|
||||
payable
|
||||
{
|
||||
sellToken = sellToken_;
|
||||
buyToken = buyToken_;
|
||||
buyAmount = buyAmount_;
|
||||
}
|
||||
|
||||
receive() external payable {}
|
||||
|
||||
fallback() external payable {
|
||||
bytes4 selector = abi.decode(msg.data, (bytes4));
|
||||
bool shouldReturnBoughtAmount =
|
||||
(selector & RETURN_BOUGHT_AMOUNT_SELECTOR_FLAG) != 0x0;
|
||||
bytes4 baseSelector = selector & 0xffff0000;
|
||||
require(baseSelector == BASE_SWAP_SELECTOR, "TestCurve/REVERT");
|
||||
(
|
||||
int128 fromCoinIdx,
|
||||
int128 toCoinIdx,
|
||||
uint256 sellAmount,
|
||||
uint256 minBuyAmount
|
||||
) = abi.decode(msg.data[4:], (int128, int128, uint256, uint256));
|
||||
if (fromCoinIdx == SELL_TOKEN_COIN_IDX) {
|
||||
sellToken.transferFrom(msg.sender, address(this), sellAmount);
|
||||
}
|
||||
if (toCoinIdx == BUY_TOKEN_COIN_IDX) {
|
||||
buyToken.mint(msg.sender, buyAmount);
|
||||
} else if (toCoinIdx == ETH_COIN_IDX) {
|
||||
msg.sender.transfer(buyAmount);
|
||||
}
|
||||
emit CurveCalled(
|
||||
msg.value,
|
||||
selector,
|
||||
fromCoinIdx,
|
||||
toCoinIdx,
|
||||
sellAmount,
|
||||
minBuyAmount
|
||||
);
|
||||
if (shouldReturnBoughtAmount) {
|
||||
assembly {
|
||||
mstore(0, sload(buyAmount_slot))
|
||||
return(0, 32)
|
||||
}
|
||||
}
|
||||
assembly { return(0, 0) }
|
||||
}
|
||||
}
|
@ -74,7 +74,7 @@ contract TestLiquidityProvider {
|
||||
bytes calldata // auxiliaryData
|
||||
)
|
||||
external
|
||||
returns (uint256 boughtAmount)
|
||||
returns (uint256)
|
||||
{
|
||||
emit SellTokenForToken(
|
||||
inputToken,
|
||||
@ -98,7 +98,7 @@ contract TestLiquidityProvider {
|
||||
bytes calldata // auxiliaryData
|
||||
)
|
||||
external
|
||||
returns (uint256 boughtAmount)
|
||||
returns (uint256)
|
||||
{
|
||||
emit SellEthForToken(
|
||||
outputToken,
|
||||
@ -121,7 +121,7 @@ contract TestLiquidityProvider {
|
||||
bytes calldata // auxiliaryData
|
||||
)
|
||||
external
|
||||
returns (uint256 boughtAmount)
|
||||
returns (uint256)
|
||||
{
|
||||
emit SellTokenForEth(
|
||||
inputToken,
|
||||
|
@ -43,7 +43,7 @@
|
||||
"config": {
|
||||
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|BridgeSource|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|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|NativeOrdersFeature|OwnableFeature|PayTakerTransformer|PermissionlessTransformerDeployer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|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|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|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|NativeOrdersFeature|OwnableFeature|PayTakerTransformer|PermissionlessTransformerDeployer|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"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -10,6 +10,7 @@ import * as AllowanceTarget from '../test/generated-artifacts/AllowanceTarget.js
|
||||
import * as BootstrapFeature from '../test/generated-artifacts/BootstrapFeature.json';
|
||||
import * as BridgeAdapter from '../test/generated-artifacts/BridgeAdapter.json';
|
||||
import * as BridgeSource from '../test/generated-artifacts/BridgeSource.json';
|
||||
import * as CurveLiquidityProvider from '../test/generated-artifacts/CurveLiquidityProvider.json';
|
||||
import * as FeeCollector from '../test/generated-artifacts/FeeCollector.json';
|
||||
import * as FeeCollectorController from '../test/generated-artifacts/FeeCollectorController.json';
|
||||
import * as FillQuoteTransformer from '../test/generated-artifacts/FillQuoteTransformer.json';
|
||||
@ -93,6 +94,7 @@ import * as PermissionlessTransformerDeployer from '../test/generated-artifacts/
|
||||
import * as SimpleFunctionRegistryFeature from '../test/generated-artifacts/SimpleFunctionRegistryFeature.json';
|
||||
import * as TestBridge from '../test/generated-artifacts/TestBridge.json';
|
||||
import * as TestCallTarget from '../test/generated-artifacts/TestCallTarget.json';
|
||||
import * as TestCurve from '../test/generated-artifacts/TestCurve.json';
|
||||
import * as TestDelegateCaller from '../test/generated-artifacts/TestDelegateCaller.json';
|
||||
import * as TestFeeCollectorController from '../test/generated-artifacts/TestFeeCollectorController.json';
|
||||
import * as TestFillQuoteTransformerBridge from '../test/generated-artifacts/TestFillQuoteTransformerBridge.json';
|
||||
@ -186,6 +188,7 @@ export const artifacts = {
|
||||
FixinProtocolFees: FixinProtocolFees as ContractArtifact,
|
||||
FixinReentrancyGuard: FixinReentrancyGuard as ContractArtifact,
|
||||
FixinTokenSpender: FixinTokenSpender as ContractArtifact,
|
||||
CurveLiquidityProvider: CurveLiquidityProvider as ContractArtifact,
|
||||
FullMigration: FullMigration as ContractArtifact,
|
||||
InitialMigration: InitialMigration as ContractArtifact,
|
||||
LibBootstrap: LibBootstrap as ContractArtifact,
|
||||
@ -231,6 +234,7 @@ export const artifacts = {
|
||||
ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact,
|
||||
TestBridge: TestBridge as ContractArtifact,
|
||||
TestCallTarget: TestCallTarget as ContractArtifact,
|
||||
TestCurve: TestCurve as ContractArtifact,
|
||||
TestDelegateCaller: TestDelegateCaller as ContractArtifact,
|
||||
TestFeeCollectorController: TestFeeCollectorController as ContractArtifact,
|
||||
TestFillQuoteTransformerBridge: TestFillQuoteTransformerBridge as ContractArtifact,
|
||||
|
294
contracts/zero-ex/test/liqudity-providers/curve_test.ts
Normal file
294
contracts/zero-ex/test/liqudity-providers/curve_test.ts
Normal file
@ -0,0 +1,294 @@
|
||||
import { blockchainTests, constants, expect, getRandomInteger, verifyEventsFromLogs } from '@0x/contracts-test-utils';
|
||||
import { BigNumber, hexUtils } from '@0x/utils';
|
||||
|
||||
import { artifacts } from '../artifacts';
|
||||
import { CurveLiquidityProviderContract, TestCurveContract, TestMintableERC20TokenContract } from '../wrappers';
|
||||
|
||||
blockchainTests.resets('CurveLiquidityProvider feature', env => {
|
||||
let lp: CurveLiquidityProviderContract;
|
||||
let sellToken: TestMintableERC20TokenContract;
|
||||
let buyToken: TestMintableERC20TokenContract;
|
||||
let testCurve: TestCurveContract;
|
||||
let owner: string;
|
||||
let taker: string;
|
||||
const RECIPIENT = hexUtils.random(20);
|
||||
const SELL_AMOUNT = getRandomInteger('1e6', '1e18');
|
||||
const BUY_AMOUNT = getRandomInteger('1e6', '10e18');
|
||||
const REVERTING_SELECTOR = '0xdeaddead';
|
||||
const SWAP_SELECTOR = '0x12340000';
|
||||
const SWAP_WITH_RETURN_SELECTOR = '0x12340001';
|
||||
const ETH_TOKEN_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
||||
const SELL_TOKEN_COIN_IDX = 0;
|
||||
const BUY_TOKEN_COIN_IDX = 1;
|
||||
const ETH_COIN_IDX = 2;
|
||||
const { ZERO_AMOUNT } = constants;
|
||||
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
testCurve = await TestCurveContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestCurve,
|
||||
env.provider,
|
||||
{ ...env.txDefaults, value: BUY_AMOUNT },
|
||||
artifacts,
|
||||
sellToken.address,
|
||||
buyToken.address,
|
||||
BUY_AMOUNT,
|
||||
);
|
||||
lp = await CurveLiquidityProviderContract.deployFrom0xArtifactAsync(
|
||||
artifacts.CurveLiquidityProvider,
|
||||
env.provider,
|
||||
{ ...env.txDefaults, from: taker },
|
||||
artifacts,
|
||||
);
|
||||
});
|
||||
|
||||
interface CurveDataFields {
|
||||
curveAddress: string;
|
||||
exchangeFunctionSelector: string;
|
||||
fromCoinIdx: number;
|
||||
toCoinIdx: number;
|
||||
}
|
||||
|
||||
async function fundProviderContractAsync(fromCoinIdx: number, amount: BigNumber = SELL_AMOUNT): Promise<void> {
|
||||
if (fromCoinIdx === SELL_TOKEN_COIN_IDX) {
|
||||
await sellToken.mint(lp.address, SELL_AMOUNT).awaitTransactionSuccessAsync();
|
||||
} else {
|
||||
await env.web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await env.web3Wrapper.sendTransactionAsync({
|
||||
from: taker,
|
||||
to: lp.address,
|
||||
value: SELL_AMOUNT,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function encodeCurveData(fields: Partial<CurveDataFields> = {}): string {
|
||||
const _fields = {
|
||||
curveAddress: testCurve.address,
|
||||
exchangeFunctionSelector: SWAP_SELECTOR,
|
||||
fromCoinIdx: SELL_TOKEN_COIN_IDX,
|
||||
toCoinIdx: BUY_TOKEN_COIN_IDX,
|
||||
...fields,
|
||||
};
|
||||
return hexUtils.concat(
|
||||
hexUtils.leftPad(_fields.curveAddress),
|
||||
hexUtils.rightPad(_fields.exchangeFunctionSelector),
|
||||
hexUtils.leftPad(_fields.fromCoinIdx),
|
||||
hexUtils.leftPad(_fields.toCoinIdx),
|
||||
);
|
||||
}
|
||||
|
||||
it('can swap ERC20->ERC20', async () => {
|
||||
await fundProviderContractAsync(SELL_TOKEN_COIN_IDX);
|
||||
const call = lp.sellTokenForToken(
|
||||
sellToken.address,
|
||||
buyToken.address,
|
||||
RECIPIENT,
|
||||
BUY_AMOUNT,
|
||||
encodeCurveData(),
|
||||
);
|
||||
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,
|
||||
selector: SWAP_SELECTOR,
|
||||
fromCoinIdx: new BigNumber(SELL_TOKEN_COIN_IDX),
|
||||
toCoinIdx: new BigNumber(BUY_TOKEN_COIN_IDX),
|
||||
sellAmount: SELL_AMOUNT,
|
||||
minBuyAmount: BUY_AMOUNT,
|
||||
},
|
||||
],
|
||||
'CurveCalled',
|
||||
);
|
||||
});
|
||||
|
||||
it('can swap ERC20->ETH', async () => {
|
||||
await fundProviderContractAsync(SELL_TOKEN_COIN_IDX);
|
||||
const call = lp.sellTokenForEth(
|
||||
sellToken.address,
|
||||
RECIPIENT,
|
||||
BUY_AMOUNT,
|
||||
encodeCurveData({ toCoinIdx: ETH_COIN_IDX }),
|
||||
);
|
||||
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,
|
||||
selector: SWAP_SELECTOR,
|
||||
fromCoinIdx: new BigNumber(SELL_TOKEN_COIN_IDX),
|
||||
toCoinIdx: new BigNumber(ETH_COIN_IDX),
|
||||
sellAmount: SELL_AMOUNT,
|
||||
minBuyAmount: BUY_AMOUNT,
|
||||
},
|
||||
],
|
||||
'CurveCalled',
|
||||
);
|
||||
});
|
||||
|
||||
it('can swap ETH->ERC20', async () => {
|
||||
await fundProviderContractAsync(ETH_COIN_IDX);
|
||||
const call = lp.sellEthForToken(
|
||||
buyToken.address,
|
||||
RECIPIENT,
|
||||
BUY_AMOUNT,
|
||||
encodeCurveData({ fromCoinIdx: ETH_COIN_IDX }),
|
||||
);
|
||||
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,
|
||||
selector: SWAP_SELECTOR,
|
||||
fromCoinIdx: new BigNumber(ETH_COIN_IDX),
|
||||
toCoinIdx: new BigNumber(BUY_TOKEN_COIN_IDX),
|
||||
sellAmount: SELL_AMOUNT,
|
||||
minBuyAmount: BUY_AMOUNT,
|
||||
},
|
||||
],
|
||||
'CurveCalled',
|
||||
);
|
||||
});
|
||||
|
||||
it('can swap ETH->ERC20 with attached ETH', async () => {
|
||||
const call = lp.sellEthForToken(
|
||||
buyToken.address,
|
||||
RECIPIENT,
|
||||
BUY_AMOUNT,
|
||||
encodeCurveData({ fromCoinIdx: ETH_COIN_IDX }),
|
||||
);
|
||||
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,
|
||||
selector: SWAP_SELECTOR,
|
||||
fromCoinIdx: new BigNumber(ETH_COIN_IDX),
|
||||
toCoinIdx: new BigNumber(BUY_TOKEN_COIN_IDX),
|
||||
sellAmount: SELL_AMOUNT,
|
||||
minBuyAmount: BUY_AMOUNT,
|
||||
},
|
||||
],
|
||||
'CurveCalled',
|
||||
);
|
||||
});
|
||||
|
||||
it('can swap with a pool that returns bought amount', async () => {
|
||||
await fundProviderContractAsync(SELL_TOKEN_COIN_IDX);
|
||||
const call = lp.sellTokenForToken(
|
||||
sellToken.address,
|
||||
buyToken.address,
|
||||
RECIPIENT,
|
||||
BUY_AMOUNT,
|
||||
encodeCurveData({ exchangeFunctionSelector: SWAP_WITH_RETURN_SELECTOR }),
|
||||
);
|
||||
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,
|
||||
selector: SWAP_WITH_RETURN_SELECTOR,
|
||||
fromCoinIdx: new BigNumber(SELL_TOKEN_COIN_IDX),
|
||||
toCoinIdx: new BigNumber(BUY_TOKEN_COIN_IDX),
|
||||
sellAmount: SELL_AMOUNT,
|
||||
minBuyAmount: BUY_AMOUNT,
|
||||
},
|
||||
],
|
||||
'CurveCalled',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts if pool reverts', async () => {
|
||||
await fundProviderContractAsync(SELL_TOKEN_COIN_IDX);
|
||||
const call = lp.sellTokenForToken(
|
||||
sellToken.address,
|
||||
buyToken.address,
|
||||
RECIPIENT,
|
||||
BUY_AMOUNT,
|
||||
encodeCurveData({ exchangeFunctionSelector: REVERTING_SELECTOR }),
|
||||
);
|
||||
return expect(call.callAsync()).to.revertWith('TestCurve/REVERT');
|
||||
});
|
||||
|
||||
it('reverts if underbought', async () => {
|
||||
await fundProviderContractAsync(SELL_TOKEN_COIN_IDX);
|
||||
const call = lp.sellTokenForToken(
|
||||
sellToken.address,
|
||||
buyToken.address,
|
||||
RECIPIENT,
|
||||
BUY_AMOUNT.plus(1),
|
||||
encodeCurveData(),
|
||||
);
|
||||
return expect(call.callAsync()).to.revertWith('CurveLiquidityProvider/UNDERBOUGHT');
|
||||
});
|
||||
|
||||
it('reverts if ERC20->ERC20 receives an ETH input token', async () => {
|
||||
await fundProviderContractAsync(ETH_COIN_IDX);
|
||||
const call = lp.sellTokenForToken(
|
||||
ETH_TOKEN_ADDRESS,
|
||||
buyToken.address,
|
||||
RECIPIENT,
|
||||
BUY_AMOUNT,
|
||||
encodeCurveData(),
|
||||
);
|
||||
return expect(call.callAsync()).to.revertWith('CurveLiquidityProvider/INVALID_ARGS');
|
||||
});
|
||||
|
||||
it('reverts if ERC20->ERC20 receives an ETH output token', async () => {
|
||||
await fundProviderContractAsync(SELL_TOKEN_COIN_IDX);
|
||||
const call = lp.sellTokenForToken(
|
||||
sellToken.address,
|
||||
ETH_TOKEN_ADDRESS,
|
||||
RECIPIENT,
|
||||
BUY_AMOUNT,
|
||||
encodeCurveData(),
|
||||
);
|
||||
return expect(call.callAsync()).to.revertWith('CurveLiquidityProvider/INVALID_ARGS');
|
||||
});
|
||||
|
||||
it('reverts if ERC20->ETH receives an ETH input token', async () => {
|
||||
await fundProviderContractAsync(SELL_TOKEN_COIN_IDX);
|
||||
const call = lp.sellTokenForEth(ETH_TOKEN_ADDRESS, RECIPIENT, BUY_AMOUNT, encodeCurveData());
|
||||
return expect(call.callAsync()).to.revertWith('CurveLiquidityProvider/INVALID_ARGS');
|
||||
});
|
||||
|
||||
it('reverts if ETH->ERC20 receives an ETH output token', async () => {
|
||||
await fundProviderContractAsync(ETH_COIN_IDX);
|
||||
const call = lp.sellEthForToken(ETH_TOKEN_ADDRESS, RECIPIENT, BUY_AMOUNT, encodeCurveData());
|
||||
return expect(call.callAsync()).to.revertWith('CurveLiquidityProvider/INVALID_ARGS');
|
||||
});
|
||||
});
|
@ -8,6 +8,7 @@ export * from '../test/generated-wrappers/allowance_target';
|
||||
export * from '../test/generated-wrappers/bootstrap_feature';
|
||||
export * from '../test/generated-wrappers/bridge_adapter';
|
||||
export * from '../test/generated-wrappers/bridge_source';
|
||||
export * from '../test/generated-wrappers/curve_liquidity_provider';
|
||||
export * from '../test/generated-wrappers/fee_collector';
|
||||
export * from '../test/generated-wrappers/fee_collector_controller';
|
||||
export * from '../test/generated-wrappers/fill_quote_transformer';
|
||||
@ -91,6 +92,7 @@ export * from '../test/generated-wrappers/permissionless_transformer_deployer';
|
||||
export * from '../test/generated-wrappers/simple_function_registry_feature';
|
||||
export * from '../test/generated-wrappers/test_bridge';
|
||||
export * from '../test/generated-wrappers/test_call_target';
|
||||
export * from '../test/generated-wrappers/test_curve';
|
||||
export * from '../test/generated-wrappers/test_delegate_caller';
|
||||
export * from '../test/generated-wrappers/test_fee_collector_controller';
|
||||
export * from '../test/generated-wrappers/test_fill_quote_transformer_bridge';
|
||||
|
@ -36,6 +36,7 @@
|
||||
"test/generated-artifacts/BootstrapFeature.json",
|
||||
"test/generated-artifacts/BridgeAdapter.json",
|
||||
"test/generated-artifacts/BridgeSource.json",
|
||||
"test/generated-artifacts/CurveLiquidityProvider.json",
|
||||
"test/generated-artifacts/FeeCollector.json",
|
||||
"test/generated-artifacts/FeeCollectorController.json",
|
||||
"test/generated-artifacts/FillQuoteTransformer.json",
|
||||
@ -119,6 +120,7 @@
|
||||
"test/generated-artifacts/SimpleFunctionRegistryFeature.json",
|
||||
"test/generated-artifacts/TestBridge.json",
|
||||
"test/generated-artifacts/TestCallTarget.json",
|
||||
"test/generated-artifacts/TestCurve.json",
|
||||
"test/generated-artifacts/TestDelegateCaller.json",
|
||||
"test/generated-artifacts/TestFeeCollectorController.json",
|
||||
"test/generated-artifacts/TestFillQuoteTransformerBridge.json",
|
||||
|
@ -5,6 +5,14 @@
|
||||
{
|
||||
"note": "Filter MultiHop where second source is not present",
|
||||
"pr": 138
|
||||
},
|
||||
{
|
||||
"note": "Add CurveLiquidityProvider \"direct\" route to EP consumer.",
|
||||
"pr": 127
|
||||
},
|
||||
{
|
||||
"note": "Fix compiler error on `ILiquidityProvider` call",
|
||||
"pr": 127
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -58,7 +58,11 @@ contract LiquidityProviderSampler is
|
||||
try
|
||||
ILiquidityProvider(providerAddress).getSellQuote
|
||||
{gas: DEFAULT_CALL_GAS}
|
||||
(takerToken, makerToken, takerTokenAmounts[i])
|
||||
(
|
||||
IERC20TokenV06(takerToken),
|
||||
IERC20TokenV06(makerToken),
|
||||
takerTokenAmounts[i]
|
||||
)
|
||||
returns (uint256 amount)
|
||||
{
|
||||
makerTokenAmounts[i] = amount;
|
||||
|
@ -2,6 +2,7 @@ import { ContractAddresses } from '@0x/contract-addresses';
|
||||
import { IZeroExContract } from '@0x/contract-wrappers';
|
||||
import {
|
||||
encodeAffiliateFeeTransformerData,
|
||||
encodeCurveLiquidityProviderData,
|
||||
encodeFillQuoteTransformerData,
|
||||
encodePayTakerTransformerData,
|
||||
encodeWethTransformerData,
|
||||
@ -29,11 +30,13 @@ import {
|
||||
SwapQuoteGetOutputOpts,
|
||||
} from '../types';
|
||||
import { assert } from '../utils/assert';
|
||||
import { CURVE_LIQUIDITY_PROVIDER_BY_CHAIN_ID } from '../utils/market_operation_utils/constants';
|
||||
import {
|
||||
createBridgeDataForBridgeOrder,
|
||||
getERC20BridgeSourceToBridgeSource,
|
||||
} from '../utils/market_operation_utils/orders';
|
||||
import {
|
||||
CurveFillData,
|
||||
ERC20BridgeSource,
|
||||
LiquidityProviderFillData,
|
||||
NativeLimitOrderFillData,
|
||||
@ -166,6 +169,31 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
};
|
||||
}
|
||||
|
||||
if (isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.Curve, ERC20BridgeSource.Swerve])) {
|
||||
const fillData = quote.orders[0].fills[0].fillData as CurveFillData;
|
||||
return {
|
||||
calldataHexString: this._exchangeProxy
|
||||
.sellToLiquidityProvider(
|
||||
isFromETH ? ETH_TOKEN_ADDRESS : sellToken,
|
||||
isToETH ? ETH_TOKEN_ADDRESS : buyToken,
|
||||
CURVE_LIQUIDITY_PROVIDER_BY_CHAIN_ID[this.chainId],
|
||||
NULL_ADDRESS,
|
||||
sellAmount,
|
||||
minBuyAmount,
|
||||
encodeCurveLiquidityProviderData({
|
||||
curveAddress: fillData.pool.poolAddress,
|
||||
exchangeFunctionSelector: fillData.pool.exchangeFunctionSelector,
|
||||
fromCoinIdx: new BigNumber(fillData.fromTokenIdx),
|
||||
toCoinIdx: new BigNumber(fillData.toTokenIdx),
|
||||
}),
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
||||
toAddress: this._exchangeProxy.address,
|
||||
allowanceTarget: this.contractAddresses.exchangeProxyAllowanceTarget,
|
||||
};
|
||||
}
|
||||
|
||||
// Build up the transforms.
|
||||
const transforms = [];
|
||||
if (isFromETH) {
|
||||
|
@ -422,6 +422,12 @@ export const MAINNET_MOONISWAP_V2_1_REGISTRY = '0xbaf9a5d4b0052359326a6cdab54bab
|
||||
|
||||
export const MAINNET_DODO_HELPER = '0x533da777aedce766ceae696bf90f8541a4ba80eb';
|
||||
|
||||
export const CURVE_LIQUIDITY_PROVIDER_BY_CHAIN_ID: { [id: string]: string } = {
|
||||
'1': '0xe3a207e4225d459095491ea75d30b31968dff887',
|
||||
'3': '0xe3a207e4225d459095491ea75d30b31968dff887',
|
||||
'1337': '0xe3a207e4225d459095491ea75d30b31968dff887',
|
||||
};
|
||||
|
||||
export const MAINNET_SHELL_POOLS = {
|
||||
StableCoins: {
|
||||
poolAddress: '0x8f26d7bab7a73309141a291525c965ecdea7bf42',
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "1.3.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add VIP utils",
|
||||
"pr": 127
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "1.2.0",
|
||||
"changes": [
|
||||
|
@ -8,3 +8,4 @@ export * from './meta_transactions';
|
||||
export * from './signature_utils';
|
||||
export * from './transformer_utils';
|
||||
export * from './constants';
|
||||
export * from './vip_utils';
|
||||
|
27
packages/protocol-utils/src/vip_utils.ts
Normal file
27
packages/protocol-utils/src/vip_utils.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { AbiEncoder, BigNumber } from '@0x/utils';
|
||||
|
||||
export interface CurveLiquidityProviderData {
|
||||
curveAddress: string;
|
||||
exchangeFunctionSelector: string;
|
||||
fromCoinIdx: BigNumber;
|
||||
toCoinIdx: BigNumber;
|
||||
}
|
||||
|
||||
export const curveLiquidityProviderDataEncoder = AbiEncoder.create([
|
||||
{ name: 'curveAddress', type: 'address' },
|
||||
{ name: 'exchangeFunctionSelector', type: 'bytes4' },
|
||||
{ name: 'fromCoinIdx', type: 'int128' },
|
||||
{ name: 'toCoinIdx', type: 'int128' },
|
||||
]);
|
||||
|
||||
/**
|
||||
* Encode data for the curve liquidity provider contract.
|
||||
*/
|
||||
export function encodeCurveLiquidityProviderData(data: CurveLiquidityProviderData): string {
|
||||
return curveLiquidityProviderDataEncoder.encode([
|
||||
data.curveAddress,
|
||||
data.exchangeFunctionSelector,
|
||||
data.fromCoinIdx,
|
||||
data.toCoinIdx,
|
||||
]);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user