Compare commits

..

12 Commits

Author SHA1 Message Date
Github Actions
6d877d5242 Publish
- @0x/contracts-integrations@2.7.35
 - @0x/asset-swapper@6.6.0
2021-04-16 17:07:22 +00:00
Github Actions
e4abd690e7 Updated CHANGELOGS & MD docs 2021-04-16 17:07:17 +00:00
Jacob Evans
2a194384b6 [asset-swapper] Support Ropsten testnet (#203) 2021-04-16 13:55:35 +10:00
Jacob Evans
1e069e6f8a Remove 10 days from protocol fees docs (#202) 2021-04-16 10:51:41 +10:00
Daniel Pyrathon
a019bb913d only set Last Look parameter if it's explicitly set (#200)
* only set Last Look parameter if it's explicitly set

* remove expoectation of having isLastLook
2021-04-14 16:38:55 -07:00
Github Actions
9ce73931f7 Publish
- @0x/contracts-integrations@2.7.34
 - @0x/asset-swapper@6.5.3
2021-04-14 20:56:21 +00:00
Github Actions
97020df178 Updated CHANGELOGS & MD docs 2021-04-14 20:56:14 +00:00
Lawrence Forman
dfb7b3de8f Apply slippage to non-native orders [TKR-39] (#198)
* `@0x/asset-swapper`: Apply slippage to FQT bridge orders

* review comments

Co-authored-by: Lawrence Forman <me@merklejerk.com>
2021-04-13 19:24:50 -04:00
Github Actions
9e152912fe Publish
- @0x/contracts-integrations@2.7.33
 - @0x/asset-swapper@6.5.2
2021-04-13 11:51:04 +00:00
Github Actions
b2c2f1e1aa Updated CHANGELOGS & MD docs 2021-04-13 11:50:57 +00:00
Jacob Evans
629c7d8e92 Fix asset-swapper test (#199) 2021-04-13 21:01:26 +10:00
Jacob Evans
62f24d4356 [asset-swapper] add Native fee token on all chains (#191) 2021-04-13 08:21:26 +10:00
13 changed files with 148 additions and 37 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-integrations",
"version": "2.7.32",
"version": "2.7.35",
"private": true,
"engines": {
"node": ">=6.12"
@@ -93,7 +93,7 @@
"typescript": "4.2.2"
},
"dependencies": {
"@0x/asset-swapper": "^6.5.1",
"@0x/asset-swapper": "^6.6.0",
"@0x/base-contract": "^6.2.18",
"@0x/contracts-asset-proxy": "^3.7.9",
"@0x/contracts-erc1155": "^2.1.27",

View File

@@ -4,7 +4,7 @@ Protocol Fees
An ETH protocol fee is paid by the Taker each time a `Limit Order <./orders.html#limit-orders>`_ is `filled <./functions.html>`_.
The fee is proportional to the gas cost of filling an order and scales linearly with gas price. The cost is currently ``70k * tx.gasprice``.
Every 10 days, these fees are aggregated and distributed to the makers as a liquidity reward: the reward is proportional to the maker's collected fees and staked ZRX relative to other makers.
At the end of every Staking Epoch, these fees are aggregated and distributed to the makers as a liquidity reward: the reward is proportional to the maker's collected fees and staked ZRX relative to other makers.
To learn more about protocol fees and liquidity incentives, see the `Official Spec <https://github.com/0xProject/0x-protocol-specification/blob/master/staking/staking-specification.md>`_.
.. note::

View File

@@ -1,4 +1,33 @@
[
{
"version": "6.6.0",
"changes": [
{
"note": "Support `Ropsten` network",
"pr": 203
}
],
"timestamp": 1618592834
},
{
"version": "6.5.3",
"changes": [
{
"note": "Apply slippage to bridge orders in consumer",
"pr": 198
}
],
"timestamp": 1618433771
},
{
"timestamp": 1618314654,
"version": "6.5.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1618259868,
"version": "6.5.1",

View File

@@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v6.6.0 - _April 16, 2021_
* Support `Ropsten` network (#203)
## v6.5.3 - _April 14, 2021_
* Apply slippage to bridge orders in consumer (#198)
## v6.5.2 - _April 13, 2021_
* Dependencies updated
## v6.5.1 - _April 12, 2021_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/asset-swapper",
"version": "6.5.1",
"version": "6.6.0",
"engines": {
"node": ">=6.12"
},

View File

@@ -23,6 +23,7 @@ import {
CalldataInfo,
ExchangeProxyContractOpts,
MarketBuySwapQuote,
MarketOperation,
MarketSellSwapQuote,
SwapQuote,
SwapQuoteConsumerBase,
@@ -43,6 +44,7 @@ import {
LiquidityProviderFillData,
MooniswapFillData,
OptimizedMarketBridgeOrder,
OptimizedMarketOrder,
UniswapV2FillData,
} from '../utils/market_operation_utils/types';
@@ -136,7 +138,10 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
const buyToken = quote.makerToken;
// Take the bounds from the worst case
const sellAmount = quote.worstCaseQuoteInfo.totalTakerAmount;
const sellAmount = BigNumber.max(
quote.bestCaseQuoteInfo.totalTakerAmount,
quote.worstCaseQuoteInfo.totalTakerAmount,
);
let minBuyAmount = quote.worstCaseQuoteInfo.makerAmount;
let ethAmount = quote.worstCaseQuoteInfo.protocolFeeInWeiAmount;
@@ -144,13 +149,15 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
ethAmount = ethAmount.plus(sellAmount);
}
const slippedOrders = slipNonNativeOrders(quote);
// VIP routes.
if (
this.chainId === ChainId.Mainnet &&
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.UniswapV2, ERC20BridgeSource.SushiSwap])
) {
const source = quote.orders[0].source;
const fillData = (quote.orders[0] as OptimizedMarketBridgeOrder<UniswapV2FillData>).fillData;
const source = slippedOrders[0].source;
const fillData = (slippedOrders[0] as OptimizedMarketBridgeOrder<UniswapV2FillData>).fillData;
return {
calldataHexString: this._exchangeProxy
.sellToUniswap(
@@ -183,8 +190,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
ERC20BridgeSource.SushiSwap,
])
) {
const source = quote.orders[0].source;
const fillData = (quote.orders[0] as OptimizedMarketBridgeOrder<UniswapV2FillData>).fillData;
const source = slippedOrders[0].source;
const fillData = (slippedOrders[0] as OptimizedMarketBridgeOrder<UniswapV2FillData>).fillData;
return {
calldataHexString: this._exchangeProxy
.sellToPancakeSwap(
@@ -213,7 +220,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
this.chainId === ChainId.Mainnet &&
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.LiquidityProvider])
) {
const fillData = (quote.orders[0] as OptimizedMarketBridgeOrder<LiquidityProviderFillData>).fillData;
const fillData = (slippedOrders[0] as OptimizedMarketBridgeOrder<LiquidityProviderFillData>).fillData;
const target = fillData.poolAddress;
return {
calldataHexString: this._exchangeProxy
@@ -238,7 +245,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
this.chainId === ChainId.Mainnet &&
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.Curve, ERC20BridgeSource.Swerve])
) {
const fillData = quote.orders[0].fills[0].fillData as CurveFillData;
const fillData = slippedOrders[0].fills[0].fillData as CurveFillData;
return {
calldataHexString: this._exchangeProxy
.sellToLiquidityProvider(
@@ -267,7 +274,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
this.chainId === ChainId.Mainnet &&
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.Mooniswap])
) {
const fillData = quote.orders[0].fills[0].fillData as MooniswapFillData;
const fillData = slippedOrders[0].fills[0].fillData as MooniswapFillData;
return {
calldataHexString: this._exchangeProxy
.sellToLiquidityProvider(
@@ -289,7 +296,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
if (this.chainId === ChainId.Mainnet && isMultiplexBatchFillCompatible(quote, optsWithDefaults)) {
return {
calldataHexString: this._encodeMultiplexBatchFillCalldata(quote),
calldataHexString: this._encodeMultiplexBatchFillCalldata({ ...quote, orders: slippedOrders }),
ethAmount,
toAddress: this._exchangeProxy.address,
allowanceTarget: this._exchangeProxy.address,
@@ -298,7 +305,10 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
}
if (this.chainId === ChainId.Mainnet && isMultiplexMultiHopFillCompatible(quote, optsWithDefaults)) {
return {
calldataHexString: this._encodeMultiplexMultiHopFillCalldata(quote, optsWithDefaults),
calldataHexString: this._encodeMultiplexMultiHopFillCalldata(
{ ...quote, orders: slippedOrders },
optsWithDefaults,
),
ethAmount,
toAddress: this._exchangeProxy.address,
allowanceTarget: this._exchangeProxy.address,
@@ -321,10 +331,10 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
// If it's two hop we have an intermediate token this is needed to encode the individual FQT
// and we also want to ensure no dust amount is left in the flash wallet
const intermediateToken = quote.isTwoHop ? quote.orders[0].makerToken : NULL_ADDRESS;
const intermediateToken = quote.isTwoHop ? slippedOrders[0].makerToken : NULL_ADDRESS;
// This transformer will fill the quote.
if (quote.isTwoHop) {
const [firstHopOrder, secondHopOrder] = quote.orders;
const [firstHopOrder, secondHopOrder] = slippedOrders;
transforms.push({
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
data: encodeFillQuoteTransformerData({
@@ -349,14 +359,13 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
});
} else {
const fillAmount = isBuyQuote(quote) ? quote.makerTokenFillAmount : quote.takerTokenFillAmount;
transforms.push({
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
data: encodeFillQuoteTransformerData({
side: isBuyQuote(quote) ? FillQuoteTransformerSide.Buy : FillQuoteTransformerSide.Sell,
sellToken,
buyToken,
...getFQTTransformerDataFromOptimizedOrders(quote.orders),
...getFQTTransformerDataFromOptimizedOrders(slippedOrders),
refundReceiver: refundReceiver || NULL_ADDRESS,
fillAmount: !isBuyQuote(quote) && shouldSellEntireBalance ? MAX_UINT256 : fillAmount,
}),
@@ -598,3 +607,38 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
.getABIEncodedTransactionData();
}
}
function slipNonNativeOrders(quote: MarketSellSwapQuote | MarketBuySwapQuote): OptimizedMarketOrder[] {
const slippage = getMaxQuoteSlippageRate(quote);
if (!slippage) {
return quote.orders;
}
return quote.orders.map(o => {
if (o.source === ERC20BridgeSource.Native) {
return o;
}
return {
...o,
...(quote.type === MarketOperation.Sell
? { makerAmount: o.makerAmount.times(1 - slippage).integerValue(BigNumber.ROUND_DOWN) }
: { takerAmount: o.takerAmount.times(1 + slippage).integerValue(BigNumber.ROUND_UP) }),
};
});
}
function getMaxQuoteSlippageRate(quote: MarketBuySwapQuote | MarketSellSwapQuote): number {
if (quote.type === MarketOperation.Buy) {
// (worstCaseTaker - bestCaseTaker) / bestCaseTaker
// where worstCaseTaker >= bestCaseTaker
return quote.worstCaseQuoteInfo.takerAmount
.minus(quote.bestCaseQuoteInfo.takerAmount)
.div(quote.bestCaseQuoteInfo.takerAmount)
.toNumber();
}
// (bestCaseMaker - worstCaseMaker) / bestCaseMaker
// where bestCaseMaker >= worstCaseMaker
return quote.bestCaseQuoteInfo.makerAmount
.minus(quote.worstCaseQuoteInfo.makerAmount)
.div(quote.bestCaseQuoteInfo.makerAmount)
.toNumber();
}

View File

@@ -591,9 +591,10 @@ function calculateTwoHopQuoteInfo(
: secondHopOrder.makerAmount,
takerAmount: MarketOperation.Sell
? firstHopOrder.takerAmount
: // tslint:disable-next-line: binary-expression-operand-order
firstHopOrder.takerAmount.times(1 + slippage).integerValue(),
totalTakerAmount: firstHopOrder.takerAmount,
: firstHopOrder.takerAmount.times(1 + slippage).integerValue(),
totalTakerAmount: MarketOperation.Sell
? firstHopOrder.takerAmount
: firstHopOrder.takerAmount.times(1 + slippage).integerValue(),
feeTakerTokenAmount: constants.ZERO_AMOUNT,
protocolFeeInWeiAmount: constants.ZERO_AMOUNT,
gas,

View File

@@ -1,4 +1,4 @@
import { ChainId } from '@0x/contract-addresses';
import { ChainId, getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
import { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
import { BigNumber } from '@0x/utils';
import { formatBytes32String } from '@ethersproject/strings';
@@ -87,7 +87,13 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.Component,
ERC20BridgeSource.Saddle,
]),
[ChainId.Ropsten]: new SourceFilters([ERC20BridgeSource.Native]),
[ChainId.Ropsten]: new SourceFilters([
ERC20BridgeSource.Kyber,
ERC20BridgeSource.Native,
ERC20BridgeSource.SushiSwap,
ERC20BridgeSource.Uniswap,
ERC20BridgeSource.UniswapV2,
]),
[ChainId.Rinkeby]: new SourceFilters([ERC20BridgeSource.Native]),
[ChainId.Kovan]: new SourceFilters([ERC20BridgeSource.Native]),
[ChainId.Ganache]: new SourceFilters([ERC20BridgeSource.Native]),
@@ -142,7 +148,13 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.Component,
ERC20BridgeSource.Saddle,
]),
[ChainId.Ropsten]: new SourceFilters([ERC20BridgeSource.Native]),
[ChainId.Ropsten]: new SourceFilters([
ERC20BridgeSource.Kyber,
ERC20BridgeSource.Native,
ERC20BridgeSource.SushiSwap,
ERC20BridgeSource.Uniswap,
ERC20BridgeSource.UniswapV2,
]),
[ChainId.Rinkeby]: new SourceFilters([ERC20BridgeSource.Native]),
[ChainId.Kovan]: new SourceFilters([ERC20BridgeSource.Native]),
[ChainId.Ganache]: new SourceFilters([ERC20BridgeSource.Native]),
@@ -175,6 +187,7 @@ export const FEE_QUOTE_SOURCES_BY_CHAIN_ID = valueByChainId<ERC20BridgeSource[]>
{
[ChainId.Mainnet]: [ERC20BridgeSource.UniswapV2, ERC20BridgeSource.SushiSwap],
[ChainId.BSC]: [ERC20BridgeSource.PancakeSwap, ERC20BridgeSource.Mooniswap, ERC20BridgeSource.SushiSwap],
[ChainId.Ropsten]: [ERC20BridgeSource.UniswapV2, ERC20BridgeSource.SushiSwap],
},
[],
);
@@ -369,6 +382,7 @@ export const DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID = valueByChainId<string[]>(
'0x2170ed0880ac9a755fd29b2688956bd959f933f8', // ETH
'0x55d398326f99059ff775485246999027b3197955', // BUSD-T
],
[ChainId.Ropsten]: [getContractAddressesForChainOrThrow(ChainId.Ropsten).etherToken],
},
[],
);
@@ -397,8 +411,12 @@ export const DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID = valueByChainId<TokenAdj
export const NATIVE_FEE_TOKEN_BY_CHAIN_ID = valueByChainId<string>(
{
[ChainId.Mainnet]: MAINNET_TOKENS.WETH,
[ChainId.BSC]: '0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c', // WBNB
[ChainId.Mainnet]: getContractAddressesForChainOrThrow(ChainId.Mainnet).etherToken,
[ChainId.BSC]: getContractAddressesForChainOrThrow(ChainId.BSC).etherToken,
[ChainId.Ganache]: getContractAddressesForChainOrThrow(ChainId.Ganache).etherToken,
[ChainId.Ropsten]: getContractAddressesForChainOrThrow(ChainId.Ropsten).etherToken,
[ChainId.Rinkeby]: getContractAddressesForChainOrThrow(ChainId.Rinkeby).etherToken,
[ChainId.Kovan]: getContractAddressesForChainOrThrow(ChainId.Kovan).etherToken,
},
NULL_ADDRESS,
);
@@ -732,6 +750,11 @@ export const KYBER_CONFIG_BY_CHAIN_ID = valueByChainId<KyberSamplerOpts>(
hintHandler: '0xa1C0Fa73c39CFBcC11ec9Eb1Afc665aba9996E2C',
weth: MAINNET_TOKENS.WETH,
},
[ChainId.Ropsten]: {
networkProxy: '0x818e6fecd516ecc3849daf6845e3ec868087b755',
hintHandler: '0x63f773c026093eef988e803bdd5772dd235a8e71',
weth: getContractAddressesForChainOrThrow(ChainId.Ropsten).etherToken,
},
},
{
networkProxy: NULL_ADDRESS,
@@ -768,6 +791,7 @@ export const LIQUIDITY_PROVIDER_REGISTRY_BY_CHAIN_ID = valueByChainId<LiquidityP
export const UNISWAPV1_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
{
[ChainId.Mainnet]: '0xc0a47dfe034b400b47bdad5fecda2621de6c4d95',
[ChainId.Ropsten]: '0x9c83dce8ca20e9aaf9d3efc003b2ea62abc08351',
},
NULL_ADDRESS,
);
@@ -775,6 +799,7 @@ export const UNISWAPV1_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
export const UNISWAPV2_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
{
[ChainId.Mainnet]: '0xf164fc0ec4e93095b804a4795bbe1e041497b92a',
[ChainId.Ropsten]: '0xf164fc0ec4e93095b804a4795bbe1e041497b92a',
},
NULL_ADDRESS,
);
@@ -783,6 +808,7 @@ export const SUSHISWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
{
[ChainId.Mainnet]: '0xd9e1ce17f2641f24ae83637ab66a2cca9c378b9f',
[ChainId.BSC]: '0x1b02da8cb0d097eb8d57a175b88c7d8b47997506',
[ChainId.Ropsten]: '0x1b02da8cb0d097eb8d57a175b88c7d8b47997506',
},
NULL_ADDRESS,
);

View File

@@ -83,7 +83,7 @@ export class QuoteRequestor {
sellTokenAddress: string, // taker token
assetFillAmount: BigNumber,
comparisonPrice?: BigNumber,
isLastLook: boolean = false,
isLastLook?: boolean | undefined,
): TakerRequestQueryParams {
const { buyAmountBaseUnits, sellAmountBaseUnits } =
marketOperation === MarketOperation.Buy
@@ -111,9 +111,11 @@ export class QuoteRequestor {
buyTokenAddress,
sellTokenAddress,
comparisonPrice: comparisonPrice === undefined ? undefined : comparisonPrice.toString(),
isLastLook: isLastLook.toString(),
protocolVersion: '4',
};
if (isLastLook) {
requestParamsWithBigNumbers.isLastLook = isLastLook.toString();
}
// convert BigNumbers to strings
// so they are digestible by axios

View File

@@ -129,11 +129,10 @@ export function simulateWorstCaseFill(quoteInfo: QuoteFillInfo): QuoteFillResult
};
// Adjust the output by 1-slippage for the worst case if it is a sell
// Adjust the output by 1+slippage for the worst case if it is a buy
const outputMultiplier =
result.output =
quoteInfo.side === MarketOperation.Sell
? new BigNumber(1).minus(opts.slippage)
: new BigNumber(1).plus(opts.slippage);
result.output = result.output.times(outputMultiplier).integerValue();
? result.output.times(1 - opts.slippage).integerValue(BigNumber.ROUND_DOWN)
: result.output.times(1 + opts.slippage).integerValue(BigNumber.ROUND_UP);
return fromIntermediateQuoteFillResult(result, quoteInfo);
}

View File

@@ -915,6 +915,7 @@ describe('MarketOperationUtils tests', () => {
intentOnFilling: true,
quoteRequestor: {
requestRfqtIndicativeQuotesAsync: requestor.object.requestRfqtIndicativeQuotesAsync,
getMakerUriForSignature: requestor.object.getMakerUriForSignature,
} as any,
},
},

View File

@@ -272,7 +272,6 @@ describe('QuoteRequestor', async () => {
comparisonPrice: undefined,
takerAddress,
txOrigin,
isLastLook: 'false',
protocolVersion: '4',
};
const mockedDefaults = {
@@ -579,7 +578,6 @@ describe('QuoteRequestor', async () => {
comparisonPrice: undefined,
takerAddress,
txOrigin: takerAddress,
isLastLook: 'false',
protocolVersion: '4',
};
const mockedDefaults = {
@@ -688,7 +686,6 @@ describe('QuoteRequestor', async () => {
takerAddress,
txOrigin: takerAddress,
protocolVersion: '4',
isLastLook: 'false',
};
const mockedDefaults = {
requestApiKey: apiKey,
@@ -773,7 +770,6 @@ describe('QuoteRequestor', async () => {
comparisonPrice: undefined,
takerAddress,
txOrigin: takerAddress,
isLastLook: 'false',
protocolVersion: '4',
};
// Successful response

View File

@@ -1,7 +1,8 @@
{
"extends": ["@0x/tslint-config"],
"rules": {
"max-file-line-count": false
"max-file-line-count": false,
"binary-expression-operand-order": false
},
"linterOptions": {
"exclude": ["src/artifacts.ts", "test/artifacts.ts"]