@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:
Lawrence Forman 2021-02-11 19:13:17 -05:00 committed by GitHub
parent 72c5399b9d
commit a74a3450eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 773 additions and 61 deletions

View File

@ -1,4 +1,13 @@
[
{
"version": "0.19.0",
"changes": [
{
"note": "Add `CurveLiquidityProvider` and misc refactors",
"pr": 127
}
]
},
{
"version": "0.18.2",
"changes": [

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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));
}
}
}

View File

@ -47,8 +47,8 @@ contract MixinZeroExBridge {
sellAmount
);
boughtAmount = provider.sellTokenForToken(
address(sellToken),
address(buyToken),
sellToken,
buyToken,
address(this), // recipient
1, // minBuyAmount
lpData

View File

@ -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

View 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) }
}
}

View File

@ -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,

View File

@ -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",

View File

@ -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,

View 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');
});
});

View File

@ -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';

View File

@ -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",

View File

@ -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
}
]
},

View File

@ -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;

View File

@ -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) {

View File

@ -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',

View File

@ -1,4 +1,13 @@
[
{
"version": "1.3.0",
"changes": [
{
"note": "Add VIP utils",
"pr": 127
}
]
},
{
"version": "1.2.0",
"changes": [

View File

@ -8,3 +8,4 @@ export * from './meta_transactions';
export * from './signature_utils';
export * from './transformer_utils';
export * from './constants';
export * from './vip_utils';

View 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,
]);
}