feat: opt-in positive slippage fee for integrators (#101)
* feat: Positive Slippage Fee * fix: rename ethToTakerAssetRate to takerAssetPriceForOneEth * fix: rename takerAssetPriceForOneEth to takerAssetsPerEth * fix: export AffiliateFeeType * rebased off development * Add a gasOverhead for non-deterministic operations * CHANGELOGs * rename outputTokens to outputAmount * Confirm transformer addresses on Mainnet and Ropsten * fix import Co-authored-by: Jacob Evans <jacob@dekz.net>
This commit is contained in:
parent
5b8bbc34e8
commit
f98609686d
@ -21,6 +21,10 @@
|
||||
{
|
||||
"note": "refund ETH with no gas limit in FQT",
|
||||
"pr": 155
|
||||
},
|
||||
{
|
||||
"note": "Added an opt-in `PositiveSlippageAffiliateFee`",
|
||||
"pr": 101
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -0,0 +1,68 @@
|
||||
// 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-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||
import "../errors/LibTransformERC20RichErrors.sol";
|
||||
import "./Transformer.sol";
|
||||
import "./LibERC20Transformer.sol";
|
||||
|
||||
|
||||
/// @dev A transformer that transfers tokens to arbitrary addresses.
|
||||
contract PositiveSlippageFeeTransformer is
|
||||
Transformer
|
||||
{
|
||||
using LibRichErrorsV06 for bytes;
|
||||
using LibSafeMathV06 for uint256;
|
||||
using LibERC20Transformer for IERC20TokenV06;
|
||||
|
||||
/// @dev Information for a single fee.
|
||||
struct TokenFee {
|
||||
// The token to transfer to `recipient`.
|
||||
IERC20TokenV06 token;
|
||||
// Amount of each `token` to transfer to `recipient`.
|
||||
uint256 bestCaseAmount;
|
||||
// Recipient of `token`.
|
||||
address payable recipient;
|
||||
}
|
||||
|
||||
/// @dev Transfers tokens to recipients.
|
||||
/// @param context Context information.
|
||||
/// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`).
|
||||
function transform(TransformContext calldata context)
|
||||
external
|
||||
override
|
||||
returns (bytes4 success)
|
||||
{
|
||||
TokenFee memory fee = abi.decode(context.data, (TokenFee));
|
||||
|
||||
uint256 transformerAmount = LibERC20Transformer.getTokenBalanceOf(fee.token, address(this));
|
||||
if (transformerAmount > fee.bestCaseAmount) {
|
||||
uint256 positiveSlippageAmount = transformerAmount - fee.bestCaseAmount;
|
||||
fee.token.transformerTransfer(fee.recipient, positiveSlippageAmount);
|
||||
}
|
||||
|
||||
return LibERC20Transformer.TRANSFORMER_SUCCESS;
|
||||
}
|
||||
}
|
@ -41,9 +41,9 @@
|
||||
"rollback": "node ./lib/scripts/rollback.js"
|
||||
},
|
||||
"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,CurveLiquidityProvider",
|
||||
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|BridgeSource|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|INativeOrdersFeature|IOwnableFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinDodo|MixinDodoV2|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|NativeOrdersFeature|OwnableFeature|PayTakerTransformer|PermissionlessTransformerDeployer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestNativeOrdersFeature|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|BridgeSource|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|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|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestNativeOrdersFeature|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -29,6 +29,7 @@ import * as MetaTransactionsFeature from '../generated-artifacts/MetaTransaction
|
||||
import * as NativeOrdersFeature from '../generated-artifacts/NativeOrdersFeature.json';
|
||||
import * as OwnableFeature from '../generated-artifacts/OwnableFeature.json';
|
||||
import * as PayTakerTransformer from '../generated-artifacts/PayTakerTransformer.json';
|
||||
import * as PositiveSlippageFeeTransformer from '../generated-artifacts/PositiveSlippageFeeTransformer.json';
|
||||
import * as SimpleFunctionRegistryFeature from '../generated-artifacts/SimpleFunctionRegistryFeature.json';
|
||||
import * as TokenSpenderFeature from '../generated-artifacts/TokenSpenderFeature.json';
|
||||
import * as TransformERC20Feature from '../generated-artifacts/TransformERC20Feature.json';
|
||||
@ -48,6 +49,7 @@ export const artifacts = {
|
||||
ITransformERC20Feature: ITransformERC20Feature as ContractArtifact,
|
||||
FillQuoteTransformer: FillQuoteTransformer as ContractArtifact,
|
||||
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
|
||||
PositiveSlippageFeeTransformer: PositiveSlippageFeeTransformer as ContractArtifact,
|
||||
WethTransformer: WethTransformer as ContractArtifact,
|
||||
OwnableFeature: OwnableFeature as ContractArtifact,
|
||||
SimpleFunctionRegistryFeature: SimpleFunctionRegistryFeature as ContractArtifact,
|
||||
|
@ -46,6 +46,7 @@ export {
|
||||
IZeroExContract,
|
||||
LogMetadataTransformerContract,
|
||||
PayTakerTransformerContract,
|
||||
PositiveSlippageFeeTransformerContract,
|
||||
WethTransformerContract,
|
||||
ZeroExContract,
|
||||
} from './wrappers';
|
||||
|
@ -27,6 +27,7 @@ export * from '../generated-wrappers/meta_transactions_feature';
|
||||
export * from '../generated-wrappers/native_orders_feature';
|
||||
export * from '../generated-wrappers/ownable_feature';
|
||||
export * from '../generated-wrappers/pay_taker_transformer';
|
||||
export * from '../generated-wrappers/positive_slippage_fee_transformer';
|
||||
export * from '../generated-wrappers/simple_function_registry_feature';
|
||||
export * from '../generated-wrappers/token_spender_feature';
|
||||
export * from '../generated-wrappers/transform_erc20_feature';
|
||||
|
@ -92,6 +92,7 @@ import * as NativeOrdersFeature from '../test/generated-artifacts/NativeOrdersFe
|
||||
import * as OwnableFeature from '../test/generated-artifacts/OwnableFeature.json';
|
||||
import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json';
|
||||
import * as PermissionlessTransformerDeployer from '../test/generated-artifacts/PermissionlessTransformerDeployer.json';
|
||||
import * as PositiveSlippageFeeTransformer from '../test/generated-artifacts/PositiveSlippageFeeTransformer.json';
|
||||
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';
|
||||
@ -209,6 +210,7 @@ export const artifacts = {
|
||||
LibERC20Transformer: LibERC20Transformer as ContractArtifact,
|
||||
LogMetadataTransformer: LogMetadataTransformer as ContractArtifact,
|
||||
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
|
||||
PositiveSlippageFeeTransformer: PositiveSlippageFeeTransformer as ContractArtifact,
|
||||
Transformer: Transformer as ContractArtifact,
|
||||
WethTransformer: WethTransformer as ContractArtifact,
|
||||
BridgeAdapter: BridgeAdapter as ContractArtifact,
|
||||
|
@ -0,0 +1,127 @@
|
||||
import { blockchainTests, constants, expect, getRandomInteger, randomAddress } from '@0x/contracts-test-utils';
|
||||
import { encodePositiveSlippageFeeTransformerData } from '@0x/protocol-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { artifacts } from '../artifacts';
|
||||
import {
|
||||
PositiveSlippageFeeTransformerContract,
|
||||
TestMintableERC20TokenContract,
|
||||
TestTransformerHostContract,
|
||||
} from '../wrappers';
|
||||
|
||||
const { ZERO_AMOUNT } = constants;
|
||||
|
||||
blockchainTests.resets('PositiveSlippageFeeTransformer', env => {
|
||||
const recipient = randomAddress();
|
||||
let caller: string;
|
||||
let token: TestMintableERC20TokenContract;
|
||||
let transformer: PositiveSlippageFeeTransformerContract;
|
||||
let host: TestTransformerHostContract;
|
||||
|
||||
before(async () => {
|
||||
[caller] = await env.getAccountAddressesAsync();
|
||||
token = await TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestMintableERC20Token,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
transformer = await PositiveSlippageFeeTransformerContract.deployFrom0xArtifactAsync(
|
||||
artifacts.PositiveSlippageFeeTransformer,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
host = await TestTransformerHostContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestTransformerHost,
|
||||
env.provider,
|
||||
{ ...env.txDefaults, from: caller },
|
||||
artifacts,
|
||||
);
|
||||
});
|
||||
|
||||
interface Balances {
|
||||
ethBalance: BigNumber;
|
||||
tokenBalance: BigNumber;
|
||||
}
|
||||
|
||||
async function getBalancesAsync(owner: string): Promise<Balances> {
|
||||
return {
|
||||
ethBalance: await env.web3Wrapper.getBalanceInWeiAsync(owner),
|
||||
tokenBalance: await token.balanceOf(owner).callAsync(),
|
||||
};
|
||||
}
|
||||
|
||||
async function mintHostTokensAsync(amount: BigNumber): Promise<void> {
|
||||
await token.mint(host.address, amount).awaitTransactionSuccessAsync();
|
||||
}
|
||||
|
||||
it('does not transfer positive slippage fees when bestCaseAmount is equal to amount', async () => {
|
||||
const amount = getRandomInteger(1, '1e18');
|
||||
const data = encodePositiveSlippageFeeTransformerData({
|
||||
token: token.address,
|
||||
bestCaseAmount: amount,
|
||||
recipient,
|
||||
});
|
||||
await mintHostTokensAsync(amount);
|
||||
const beforeBalanceHost = await getBalancesAsync(host.address);
|
||||
const beforeBalanceRecipient = await getBalancesAsync(recipient);
|
||||
await host
|
||||
.rawExecuteTransform(transformer.address, {
|
||||
data,
|
||||
sender: randomAddress(),
|
||||
taker: randomAddress(),
|
||||
})
|
||||
.awaitTransactionSuccessAsync();
|
||||
expect(await getBalancesAsync(host.address)).to.deep.eq(beforeBalanceHost);
|
||||
expect(await getBalancesAsync(recipient)).to.deep.eq(beforeBalanceRecipient);
|
||||
});
|
||||
|
||||
it('does not transfer positive slippage fees when bestCaseAmount is higher than amount', async () => {
|
||||
const amount = getRandomInteger(1, '1e18');
|
||||
const bestCaseAmount = amount.times(1.1).decimalPlaces(0, BigNumber.ROUND_FLOOR);
|
||||
const data = encodePositiveSlippageFeeTransformerData({
|
||||
token: token.address,
|
||||
bestCaseAmount,
|
||||
recipient,
|
||||
});
|
||||
await mintHostTokensAsync(amount);
|
||||
const beforeBalanceHost = await getBalancesAsync(host.address);
|
||||
const beforeBalanceRecipient = await getBalancesAsync(recipient);
|
||||
await host
|
||||
.rawExecuteTransform(transformer.address, {
|
||||
data,
|
||||
sender: randomAddress(),
|
||||
taker: randomAddress(),
|
||||
})
|
||||
.awaitTransactionSuccessAsync();
|
||||
expect(await getBalancesAsync(host.address)).to.deep.eq(beforeBalanceHost);
|
||||
expect(await getBalancesAsync(recipient)).to.deep.eq(beforeBalanceRecipient);
|
||||
});
|
||||
|
||||
it('send positive slippage fee to recipient when bestCaseAmount is lower than amount', async () => {
|
||||
const amount = getRandomInteger(1, '1e18');
|
||||
const bestCaseAmount = amount.times(0.95).decimalPlaces(0, BigNumber.ROUND_FLOOR);
|
||||
const data = encodePositiveSlippageFeeTransformerData({
|
||||
token: token.address,
|
||||
bestCaseAmount,
|
||||
recipient,
|
||||
});
|
||||
await mintHostTokensAsync(amount);
|
||||
await host
|
||||
.rawExecuteTransform(transformer.address, {
|
||||
data,
|
||||
sender: randomAddress(),
|
||||
taker: randomAddress(),
|
||||
})
|
||||
.awaitTransactionSuccessAsync();
|
||||
expect(await getBalancesAsync(host.address)).to.deep.eq({
|
||||
tokenBalance: bestCaseAmount,
|
||||
ethBalance: ZERO_AMOUNT,
|
||||
});
|
||||
expect(await getBalancesAsync(recipient)).to.deep.eq({
|
||||
tokenBalance: amount.minus(bestCaseAmount), // positive slippage
|
||||
ethBalance: ZERO_AMOUNT,
|
||||
});
|
||||
});
|
||||
});
|
@ -90,6 +90,7 @@ export * from '../test/generated-wrappers/native_orders_feature';
|
||||
export * from '../test/generated-wrappers/ownable_feature';
|
||||
export * from '../test/generated-wrappers/pay_taker_transformer';
|
||||
export * from '../test/generated-wrappers/permissionless_transformer_deployer';
|
||||
export * from '../test/generated-wrappers/positive_slippage_fee_transformer';
|
||||
export * from '../test/generated-wrappers/simple_function_registry_feature';
|
||||
export * from '../test/generated-wrappers/test_bridge';
|
||||
export * from '../test/generated-wrappers/test_call_target';
|
||||
|
@ -27,6 +27,7 @@
|
||||
"generated-artifacts/NativeOrdersFeature.json",
|
||||
"generated-artifacts/OwnableFeature.json",
|
||||
"generated-artifacts/PayTakerTransformer.json",
|
||||
"generated-artifacts/PositiveSlippageFeeTransformer.json",
|
||||
"generated-artifacts/SimpleFunctionRegistryFeature.json",
|
||||
"generated-artifacts/TokenSpenderFeature.json",
|
||||
"generated-artifacts/TransformERC20Feature.json",
|
||||
@ -119,6 +120,7 @@
|
||||
"test/generated-artifacts/OwnableFeature.json",
|
||||
"test/generated-artifacts/PayTakerTransformer.json",
|
||||
"test/generated-artifacts/PermissionlessTransformerDeployer.json",
|
||||
"test/generated-artifacts/PositiveSlippageFeeTransformer.json",
|
||||
"test/generated-artifacts/SimpleFunctionRegistryFeature.json",
|
||||
"test/generated-artifacts/TestBridge.json",
|
||||
"test/generated-artifacts/TestCallTarget.json",
|
||||
|
@ -49,6 +49,10 @@
|
||||
{
|
||||
"note": "Add an alternative RFQ market making implementation",
|
||||
"pr": 139
|
||||
},
|
||||
{
|
||||
"note": "Added an opt-in `PositiveSlippageAffiliateFee`",
|
||||
"pr": 101
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -3,6 +3,7 @@ import { SignatureType } from '@0x/protocol-utils';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
|
||||
import {
|
||||
AffiliateFeeType,
|
||||
ExchangeProxyContractOpts,
|
||||
LogFunction,
|
||||
OrderPrunerOpts,
|
||||
@ -12,7 +13,11 @@ import {
|
||||
SwapQuoteRequestOpts,
|
||||
SwapQuoterOpts,
|
||||
} from './types';
|
||||
import { DEFAULT_GET_MARKET_ORDERS_OPTS, TOKENS } from './utils/market_operation_utils/constants';
|
||||
import {
|
||||
DEFAULT_GET_MARKET_ORDERS_OPTS,
|
||||
DEFAULT_INTERMEDIATE_TOKENS,
|
||||
DEFAULT_TOKEN_ADJACENCY_GRAPH,
|
||||
} from './utils/market_operation_utils/constants';
|
||||
|
||||
const ETH_GAS_STATION_API_URL = 'https://ethgasstation.info/api/ethgasAPI.json';
|
||||
const NULL_BYTES = '0x';
|
||||
@ -38,7 +43,6 @@ const PROTOCOL_FEE_MULTIPLIER = new BigNumber(70000);
|
||||
// default 50% buffer for selecting native orders to be aggregated with other sources
|
||||
const MARKET_UTILS_AMOUNT_BUFFER_PERCENTAGE = 0.5;
|
||||
|
||||
const DEFAULT_INTERMEDIATE_TOKENS = [TOKENS.WETH, TOKENS.USDT, TOKENS.DAI, TOKENS.USDC];
|
||||
const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
|
||||
chainId: ChainId.Mainnet,
|
||||
orderRefreshIntervalMs: 10000, // 10 seconds
|
||||
@ -49,12 +53,14 @@ const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
|
||||
takerApiKeyWhitelist: [],
|
||||
makerAssetOfferings: {},
|
||||
},
|
||||
tokenAdjacencyGraph: DEFAULT_TOKEN_ADJACENCY_GRAPH,
|
||||
};
|
||||
|
||||
const DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS: ExchangeProxyContractOpts = {
|
||||
isFromETH: false,
|
||||
isToETH: false,
|
||||
affiliateFee: {
|
||||
feeType: AffiliateFeeType.None,
|
||||
recipient: NULL_ADDRESS,
|
||||
buyTokenFeeAmount: ZERO_AMOUNT,
|
||||
sellTokenFeeAmount: ZERO_AMOUNT,
|
||||
@ -86,9 +92,12 @@ export const INVALID_SIGNATURE = { signatureType: SignatureType.Invalid, v: 1, r
|
||||
|
||||
export { DEFAULT_FEE_SCHEDULE, DEFAULT_GAS_SCHEDULE } from './utils/market_operation_utils/constants';
|
||||
|
||||
export const POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS = new BigNumber(30000);
|
||||
|
||||
export const constants = {
|
||||
ETH_GAS_STATION_API_URL,
|
||||
PROTOCOL_FEE_MULTIPLIER,
|
||||
POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS,
|
||||
NULL_BYTES,
|
||||
ZERO_AMOUNT,
|
||||
NULL_ADDRESS,
|
||||
|
@ -74,9 +74,10 @@ export { InsufficientAssetLiquidityError } from './errors';
|
||||
export { SwapQuoteConsumer } from './quote_consumers/swap_quote_consumer';
|
||||
export { SwapQuoter, Orderbook } from './swap_quoter';
|
||||
export {
|
||||
AffiliateFee,
|
||||
AltOffering,
|
||||
AltRfqtMakerAssetOfferings,
|
||||
AffiliateFeeType,
|
||||
AffiliateFeeAmount,
|
||||
AssetSwapperContractAddresses,
|
||||
CalldataInfo,
|
||||
ExchangeProxyContractOpts,
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
encodeCurveLiquidityProviderData,
|
||||
encodeFillQuoteTransformerData,
|
||||
encodePayTakerTransformerData,
|
||||
encodePositiveSlippageFeeTransformerData,
|
||||
encodeWethTransformerData,
|
||||
ETH_TOKEN_ADDRESS,
|
||||
FillQuoteTransformerData,
|
||||
@ -16,8 +17,9 @@ import { BigNumber, providerUtils } from '@0x/utils';
|
||||
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { constants } from '../constants';
|
||||
import { constants, POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS } from '../constants';
|
||||
import {
|
||||
AffiliateFeeType,
|
||||
CalldataInfo,
|
||||
ExchangeProxyContractOpts,
|
||||
MarketBuySwapQuote,
|
||||
@ -59,6 +61,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
payTakerTransformer: number;
|
||||
fillQuoteTransformer: number;
|
||||
affiliateFeeTransformer: number;
|
||||
positiveSlippageFeeTransformer: number;
|
||||
};
|
||||
|
||||
private readonly _exchangeProxy: IZeroExContract;
|
||||
@ -92,6 +95,10 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
contractAddresses.transformers.affiliateFeeTransformer,
|
||||
contractAddresses.exchangeProxyTransformerDeployer,
|
||||
),
|
||||
positiveSlippageFeeTransformer: findTransformerNonce(
|
||||
contractAddresses.transformers.positiveSlippageFeeTransformer,
|
||||
contractAddresses.exchangeProxyTransformerDeployer,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@ -117,7 +124,6 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
if (isFromETH) {
|
||||
ethAmount = ethAmount.plus(sellAmount);
|
||||
}
|
||||
const { buyTokenFeeAmount, sellTokenFeeAmount, recipient: feeRecipient } = affiliateFee;
|
||||
|
||||
// VIP routes.
|
||||
if (
|
||||
@ -144,7 +150,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
.getABIEncodedTransactionData(),
|
||||
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
||||
toAddress: this._exchangeProxy.address,
|
||||
allowanceTarget: this.contractAddresses.exchangeProxyAllowanceTarget,
|
||||
allowanceTarget: this._exchangeProxy.address,
|
||||
gasOverhead: ZERO_AMOUNT,
|
||||
};
|
||||
}
|
||||
|
||||
@ -165,7 +172,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
.getABIEncodedTransactionData(),
|
||||
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
||||
toAddress: this._exchangeProxy.address,
|
||||
allowanceTarget: this.contractAddresses.exchangeProxyAllowanceTarget,
|
||||
allowanceTarget: this._exchangeProxy.address,
|
||||
gasOverhead: ZERO_AMOUNT,
|
||||
};
|
||||
}
|
||||
|
||||
@ -190,7 +198,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
.getABIEncodedTransactionData(),
|
||||
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
||||
toAddress: this._exchangeProxy.address,
|
||||
allowanceTarget: this.contractAddresses.exchangeProxyAllowanceTarget,
|
||||
allowanceTarget: this._exchangeProxy.address,
|
||||
gasOverhead: ZERO_AMOUNT,
|
||||
};
|
||||
}
|
||||
|
||||
@ -262,25 +271,52 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
});
|
||||
}
|
||||
|
||||
// This transformer pays affiliate fees.
|
||||
if (buyTokenFeeAmount.isGreaterThan(0) && feeRecipient !== NULL_ADDRESS) {
|
||||
const { feeType, buyTokenFeeAmount, sellTokenFeeAmount, recipient: feeRecipient } = affiliateFee;
|
||||
let gasOverhead = ZERO_AMOUNT;
|
||||
if (feeType === AffiliateFeeType.PositiveSlippageFee && feeRecipient !== NULL_ADDRESS) {
|
||||
// bestCaseAmountWithSurplus is used to cover gas cost of sending positive slipapge fee to fee recipient
|
||||
// this helps avoid sending dust amounts which are not worth the gas cost to transfer
|
||||
let bestCaseAmountWithSurplus = quote.bestCaseQuoteInfo.makerAmount
|
||||
.plus(
|
||||
POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS.multipliedBy(quote.gasPrice).multipliedBy(
|
||||
quote.makerAmountPerEth,
|
||||
),
|
||||
)
|
||||
.integerValue();
|
||||
// In the event makerAmountPerEth is unknown, we only allow for positive slippage which is greater than
|
||||
// the best case amount
|
||||
bestCaseAmountWithSurplus = BigNumber.max(bestCaseAmountWithSurplus, quote.bestCaseQuoteInfo.makerAmount);
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.affiliateFeeTransformer,
|
||||
data: encodeAffiliateFeeTransformerData({
|
||||
fees: [
|
||||
{
|
||||
token: isToETH ? ETH_TOKEN_ADDRESS : buyToken,
|
||||
amount: buyTokenFeeAmount,
|
||||
recipient: feeRecipient,
|
||||
},
|
||||
],
|
||||
deploymentNonce: this.transformerNonces.positiveSlippageFeeTransformer,
|
||||
data: encodePositiveSlippageFeeTransformerData({
|
||||
token: isToETH ? ETH_TOKEN_ADDRESS : buyToken,
|
||||
bestCaseAmount: BigNumber.max(bestCaseAmountWithSurplus, quote.bestCaseQuoteInfo.makerAmount),
|
||||
recipient: feeRecipient,
|
||||
}),
|
||||
});
|
||||
// Adjust the minimum buy amount by the fee.
|
||||
minBuyAmount = BigNumber.max(0, minBuyAmount.minus(buyTokenFeeAmount));
|
||||
}
|
||||
if (sellTokenFeeAmount.isGreaterThan(0) && feeRecipient !== NULL_ADDRESS) {
|
||||
throw new Error('Affiliate fees denominated in sell token are not yet supported');
|
||||
// This may not be visible at eth_estimateGas time, so we explicitly add overhead
|
||||
gasOverhead = POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS;
|
||||
} else if (feeType === AffiliateFeeType.PercentageFee && feeRecipient !== NULL_ADDRESS) {
|
||||
// This transformer pays affiliate fees.
|
||||
if (buyTokenFeeAmount.isGreaterThan(0)) {
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.affiliateFeeTransformer,
|
||||
data: encodeAffiliateFeeTransformerData({
|
||||
fees: [
|
||||
{
|
||||
token: isToETH ? ETH_TOKEN_ADDRESS : buyToken,
|
||||
amount: buyTokenFeeAmount,
|
||||
recipient: feeRecipient,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
// Adjust the minimum buy amount by the fee.
|
||||
minBuyAmount = BigNumber.max(0, minBuyAmount.minus(buyTokenFeeAmount));
|
||||
}
|
||||
if (sellTokenFeeAmount.isGreaterThan(0)) {
|
||||
throw new Error('Affiliate fees denominated in sell token are not yet supported');
|
||||
}
|
||||
}
|
||||
|
||||
// The final transformer will send all funds to the taker.
|
||||
@ -306,7 +342,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
calldataHexString,
|
||||
ethAmount,
|
||||
toAddress: this._exchangeProxy.address,
|
||||
allowanceTarget: this.contractAddresses.exchangeProxyAllowanceTarget,
|
||||
allowanceTarget: this._exchangeProxy.address,
|
||||
gasOverhead,
|
||||
};
|
||||
}
|
||||
|
||||
@ -332,6 +369,10 @@ function isDirectSwapCompatible(
|
||||
if (!opts.affiliateFee.buyTokenFeeAmount.eq(0) || !opts.affiliateFee.sellTokenFeeAmount.eq(0)) {
|
||||
return false;
|
||||
}
|
||||
// Must not have a positive slippage fee.
|
||||
if (opts.affiliateFee.feeType === AffiliateFeeType.PositiveSlippageFee) {
|
||||
return false;
|
||||
}
|
||||
// Must be a single order.
|
||||
if (quote.orders.length !== 1) {
|
||||
return false;
|
||||
|
@ -454,7 +454,7 @@ function createSwapQuote(
|
||||
gasSchedule: FeeSchedule,
|
||||
slippage: number,
|
||||
): SwapQuote {
|
||||
const { optimizedOrders, quoteReport, sourceFlags, takerTokenToEthRate, makerTokenToEthRate } = optimizerResult;
|
||||
const { optimizedOrders, quoteReport, sourceFlags, takerAmountPerEth, makerAmountPerEth } = optimizerResult;
|
||||
const isTwoHop = sourceFlags === SOURCE_FLAGS[ERC20BridgeSource.MultiHop];
|
||||
|
||||
// Calculate quote info
|
||||
@ -474,8 +474,8 @@ function createSwapQuote(
|
||||
sourceBreakdown,
|
||||
makerTokenDecimals,
|
||||
takerTokenDecimals,
|
||||
takerTokenToEthRate,
|
||||
makerTokenToEthRate,
|
||||
takerAmountPerEth,
|
||||
makerAmountPerEth,
|
||||
quoteReport,
|
||||
isTwoHop,
|
||||
};
|
||||
|
@ -54,12 +54,15 @@ export interface NativeOrderFillableAmountFields {
|
||||
* toAddress: The contract address to call.
|
||||
* ethAmount: The eth amount in wei to send with the smart contract call.
|
||||
* allowanceTarget: The address the taker should grant an allowance to.
|
||||
* gasOverhead: The gas overhead needed to be added to the gas limit to allow for optional
|
||||
* operations which may not visible at eth_estimateGas time
|
||||
*/
|
||||
export interface CalldataInfo {
|
||||
calldataHexString: string;
|
||||
toAddress: string;
|
||||
ethAmount: BigNumber;
|
||||
allowanceTarget: string;
|
||||
gasOverhead: BigNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,7 +101,14 @@ export interface SwapQuoteExecutionOpts extends SwapQuoteGetOutputOpts {
|
||||
gasLimit?: number;
|
||||
}
|
||||
|
||||
export interface AffiliateFee {
|
||||
export enum AffiliateFeeType {
|
||||
None,
|
||||
PercentageFee,
|
||||
PositiveSlippageFee,
|
||||
}
|
||||
|
||||
export interface AffiliateFeeAmount {
|
||||
feeType: AffiliateFeeType;
|
||||
recipient: string;
|
||||
buyTokenFeeAmount: BigNumber;
|
||||
sellTokenFeeAmount: BigNumber;
|
||||
@ -130,7 +140,7 @@ export enum ExchangeProxyRefundReceiver {
|
||||
export interface ExchangeProxyContractOpts {
|
||||
isFromETH: boolean;
|
||||
isToETH: boolean;
|
||||
affiliateFee: AffiliateFee;
|
||||
affiliateFee: AffiliateFeeAmount;
|
||||
refundReceiver: string | ExchangeProxyRefundReceiver;
|
||||
isMetaTransaction: boolean;
|
||||
shouldSellEntireBalance: boolean;
|
||||
@ -161,8 +171,8 @@ export interface SwapQuoteBase {
|
||||
isTwoHop: boolean;
|
||||
makerTokenDecimals: number;
|
||||
takerTokenDecimals: number;
|
||||
takerTokenToEthRate: BigNumber;
|
||||
makerTokenToEthRate: BigNumber;
|
||||
takerAmountPerEth: BigNumber;
|
||||
makerAmountPerEth: BigNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,10 +60,10 @@ export function getComparisonPrices(
|
||||
}
|
||||
|
||||
// Calc native order fee penalty in output unit (maker units for sells, taker unit for buys)
|
||||
const feePenalty = !marketSideLiquidity.ethToOutputRate.isZero()
|
||||
? marketSideLiquidity.ethToOutputRate.times(feeInEth)
|
||||
const feePenalty = !marketSideLiquidity.outputAmountPerEth.isZero()
|
||||
? marketSideLiquidity.outputAmountPerEth.times(feeInEth)
|
||||
: // if it's a sell, the input token is the taker token
|
||||
marketSideLiquidity.ethToInputRate
|
||||
marketSideLiquidity.inputAmountPerEth
|
||||
.times(feeInEth)
|
||||
.times(marketSideLiquidity.side === MarketOperation.Sell ? adjustedRate : adjustedRate.pow(-1));
|
||||
|
||||
|
@ -645,6 +645,8 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
|
||||
|
||||
export const DEFAULT_FEE_SCHEDULE: Required<FeeSchedule> = { ...DEFAULT_GAS_SCHEDULE };
|
||||
|
||||
export const POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS = new BigNumber(20000);
|
||||
|
||||
// tslint:enable:custom-no-magic-numbers
|
||||
|
||||
export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
|
||||
|
@ -16,8 +16,8 @@ export function createFills(opts: {
|
||||
orders?: NativeOrderWithFillableAmounts[];
|
||||
dexQuotes?: DexSample[][];
|
||||
targetInput?: BigNumber;
|
||||
ethToOutputRate?: BigNumber;
|
||||
ethToInputRate?: BigNumber;
|
||||
outputAmountPerEth?: BigNumber;
|
||||
inputAmountPerEth?: BigNumber;
|
||||
excludedSources?: ERC20BridgeSource[];
|
||||
feeSchedule?: FeeSchedule;
|
||||
}): Fill[][] {
|
||||
@ -26,20 +26,20 @@ export function createFills(opts: {
|
||||
const feeSchedule = opts.feeSchedule || {};
|
||||
const orders = opts.orders || [];
|
||||
const dexQuotes = opts.dexQuotes || [];
|
||||
const ethToOutputRate = opts.ethToOutputRate || ZERO_AMOUNT;
|
||||
const ethToInputRate = opts.ethToInputRate || ZERO_AMOUNT;
|
||||
const outputAmountPerEth = opts.outputAmountPerEth || ZERO_AMOUNT;
|
||||
const inputAmountPerEth = opts.inputAmountPerEth || ZERO_AMOUNT;
|
||||
// Create native fills.
|
||||
const nativeFills = nativeOrdersToFills(
|
||||
side,
|
||||
orders.filter(o => o.fillableTakerAmount.isGreaterThan(0)),
|
||||
opts.targetInput,
|
||||
ethToOutputRate,
|
||||
ethToInputRate,
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
feeSchedule,
|
||||
);
|
||||
// Create DEX fills.
|
||||
const dexFills = dexQuotes.map(singleSourceSamples =>
|
||||
dexSamplesToFills(side, singleSourceSamples, ethToOutputRate, ethToInputRate, feeSchedule),
|
||||
dexSamplesToFills(side, singleSourceSamples, outputAmountPerEth, inputAmountPerEth, feeSchedule),
|
||||
);
|
||||
return [...dexFills, nativeFills]
|
||||
.map(p => clipFillsToInput(p, opts.targetInput))
|
||||
@ -75,8 +75,8 @@ function nativeOrdersToFills(
|
||||
side: MarketOperation,
|
||||
orders: NativeOrderWithFillableAmounts[],
|
||||
targetInput: BigNumber = POSITIVE_INF,
|
||||
ethToOutputRate: BigNumber,
|
||||
ethToInputRate: BigNumber,
|
||||
outputAmountPerEth: BigNumber,
|
||||
inputAmountPerEth: BigNumber,
|
||||
fees: FeeSchedule,
|
||||
): Fill[] {
|
||||
const sourcePathId = hexUtils.random();
|
||||
@ -89,9 +89,9 @@ function nativeOrdersToFills(
|
||||
const input = side === MarketOperation.Sell ? takerAmount : makerAmount;
|
||||
const output = side === MarketOperation.Sell ? makerAmount : takerAmount;
|
||||
const fee = fees[ERC20BridgeSource.Native] === undefined ? 0 : fees[ERC20BridgeSource.Native]!(o);
|
||||
const outputPenalty = !ethToOutputRate.isZero()
|
||||
? ethToOutputRate.times(fee)
|
||||
: ethToInputRate.times(fee).times(output.dividedToIntegerBy(input));
|
||||
const outputPenalty = !outputAmountPerEth.isZero()
|
||||
? outputAmountPerEth.times(fee)
|
||||
: inputAmountPerEth.times(fee).times(output.dividedToIntegerBy(input));
|
||||
// targetInput can be less than the order size
|
||||
// whilst the penalty is constant, it affects the adjusted output
|
||||
// only up until the target has been exhausted.
|
||||
@ -135,8 +135,8 @@ function nativeOrdersToFills(
|
||||
function dexSamplesToFills(
|
||||
side: MarketOperation,
|
||||
samples: DexSample[],
|
||||
ethToOutputRate: BigNumber,
|
||||
ethToInputRate: BigNumber,
|
||||
outputAmountPerEth: BigNumber,
|
||||
inputAmountPerEth: BigNumber,
|
||||
fees: FeeSchedule,
|
||||
): Fill[] {
|
||||
const sourcePathId = hexUtils.random();
|
||||
@ -156,9 +156,9 @@ function dexSamplesToFills(
|
||||
let penalty = ZERO_AMOUNT;
|
||||
if (i === 0) {
|
||||
// Only the first fill in a DEX path incurs a penalty.
|
||||
penalty = !ethToOutputRate.isZero()
|
||||
? ethToOutputRate.times(fee)
|
||||
: ethToInputRate.times(fee).times(output.dividedToIntegerBy(input));
|
||||
penalty = !outputAmountPerEth.isZero()
|
||||
? outputAmountPerEth.times(fee)
|
||||
: inputAmountPerEth.times(fee).times(output.dividedToIntegerBy(input));
|
||||
}
|
||||
const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
|
||||
|
||||
|
@ -30,7 +30,8 @@ import {
|
||||
import { createFills } from './fills';
|
||||
import { getBestTwoHopQuote } from './multihop_utils';
|
||||
import { createOrdersFromTwoHopSample } from './orders';
|
||||
import { findOptimalPathAsync } from './path_optimizer';
|
||||
import { PathPenaltyOpts } from './path';
|
||||
import { fillsToSortedPaths, findOptimalPathAsync } from './path_optimizer';
|
||||
import { DexOrderSampler, getSampleAmounts } from './sampler';
|
||||
import { SourceFilters } from './source_filters';
|
||||
import {
|
||||
@ -167,8 +168,8 @@ export class MarketOperationUtils {
|
||||
[
|
||||
tokenDecimals,
|
||||
orderFillableTakerAmounts,
|
||||
ethToMakerAssetRate,
|
||||
ethToTakerAssetRate,
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
dexQuotes,
|
||||
rawTwoHopQuotes,
|
||||
isTxOriginContract,
|
||||
@ -195,8 +196,8 @@ export class MarketOperationUtils {
|
||||
inputAmount: takerAmount,
|
||||
inputToken: takerToken,
|
||||
outputToken: makerToken,
|
||||
ethToOutputRate: ethToMakerAssetRate,
|
||||
ethToInputRate: ethToTakerAssetRate,
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
quoteSourceFilters,
|
||||
makerTokenDecimals: makerTokenDecimals.toNumber(),
|
||||
takerTokenDecimals: takerTokenDecimals.toNumber(),
|
||||
@ -321,8 +322,8 @@ export class MarketOperationUtils {
|
||||
inputAmount: makerAmount,
|
||||
inputToken: makerToken,
|
||||
outputToken: takerToken,
|
||||
ethToOutputRate: ethToTakerAssetRate,
|
||||
ethToInputRate: ethToMakerAssetRate,
|
||||
outputAmountPerEth: ethToTakerAssetRate,
|
||||
inputAmountPerEth: ethToMakerAssetRate,
|
||||
quoteSourceFilters,
|
||||
makerTokenDecimals: makerTokenDecimals.toNumber(),
|
||||
takerTokenDecimals: takerTokenDecimals.toNumber(),
|
||||
@ -392,7 +393,7 @@ export class MarketOperationUtils {
|
||||
const batchEthToTakerAssetRate = executeResults.splice(0, batchNativeOrders.length) as BigNumber[];
|
||||
const batchDexQuotes = executeResults.splice(0, batchNativeOrders.length) as DexSample[][][];
|
||||
const batchTokenDecimals = executeResults.splice(0, batchNativeOrders.length) as number[][];
|
||||
const ethToInputRate = ZERO_AMOUNT;
|
||||
const inputAmountPerEth = ZERO_AMOUNT;
|
||||
|
||||
return Promise.all(
|
||||
batchNativeOrders.map(async (nativeOrders, i) => {
|
||||
@ -401,7 +402,7 @@ export class MarketOperationUtils {
|
||||
}
|
||||
const { makerToken, takerToken } = nativeOrders[0].order;
|
||||
const orderFillableMakerAmounts = batchOrderFillableMakerAmounts[i];
|
||||
const ethToTakerAssetRate = batchEthToTakerAssetRate[i];
|
||||
const outputAmountPerEth = batchEthToTakerAssetRate[i];
|
||||
const dexQuotes = batchDexQuotes[i];
|
||||
const makerAmount = makerAmounts[i];
|
||||
try {
|
||||
@ -411,8 +412,8 @@ export class MarketOperationUtils {
|
||||
inputToken: makerToken,
|
||||
outputToken: takerToken,
|
||||
inputAmount: makerAmount,
|
||||
ethToOutputRate: ethToTakerAssetRate,
|
||||
ethToInputRate,
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
quoteSourceFilters,
|
||||
makerTokenDecimals: batchTokenDecimals[i][0],
|
||||
takerTokenDecimals: batchTokenDecimals[i][1],
|
||||
@ -455,8 +456,8 @@ export class MarketOperationUtils {
|
||||
side,
|
||||
inputAmount,
|
||||
quotes,
|
||||
ethToOutputRate,
|
||||
ethToInputRate,
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
} = marketSideLiquidity;
|
||||
const { nativeOrders, rfqtIndicativeQuotes, dexQuotes } = quotes;
|
||||
const maxFallbackSlippage = opts.maxFallbackSlippage || 0;
|
||||
@ -489,25 +490,29 @@ export class MarketOperationUtils {
|
||||
orders: [...nativeOrders, ...augmentedRfqtIndicativeQuotes],
|
||||
dexQuotes,
|
||||
targetInput: inputAmount,
|
||||
ethToOutputRate,
|
||||
ethToInputRate,
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
excludedSources: opts.excludedSources,
|
||||
feeSchedule: opts.feeSchedule,
|
||||
});
|
||||
|
||||
// Find the optimal path.
|
||||
const optimizerOpts = {
|
||||
ethToOutputRate,
|
||||
ethToInputRate,
|
||||
const penaltyOpts: PathPenaltyOpts = {
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
exchangeProxyOverhead: opts.exchangeProxyOverhead || (() => ZERO_AMOUNT),
|
||||
};
|
||||
|
||||
// NOTE: For sell quotes input is the taker asset and for buy quotes input is the maker asset
|
||||
const takerTokenToEthRate = side === MarketOperation.Sell ? ethToInputRate : ethToOutputRate;
|
||||
const makerTokenToEthRate = side === MarketOperation.Sell ? ethToOutputRate : ethToInputRate;
|
||||
const takerAmountPerEth = side === MarketOperation.Sell ? inputAmountPerEth : outputAmountPerEth;
|
||||
const makerAmountPerEth = side === MarketOperation.Sell ? outputAmountPerEth : inputAmountPerEth;
|
||||
|
||||
// Find the unoptimized best rate to calculate savings from optimizer
|
||||
const _unoptimizedPath = fillsToSortedPaths(fills, side, inputAmount, penaltyOpts)[0];
|
||||
const unoptimizedPath = _unoptimizedPath ? _unoptimizedPath.collapse(orderOpts) : undefined;
|
||||
|
||||
// Find the optimal path
|
||||
const optimalPath = await findOptimalPathAsync(side, fills, inputAmount, opts.runLimit, optimizerOpts);
|
||||
const optimalPath = await findOptimalPathAsync(side, fills, inputAmount, opts.runLimit, penaltyOpts);
|
||||
const optimalPathRate = optimalPath ? optimalPath.adjustedRate() : ZERO_AMOUNT;
|
||||
|
||||
const { adjustedRate: bestTwoHopRate, quote: bestTwoHopQuote } = getBestTwoHopQuote(
|
||||
@ -523,8 +528,9 @@ export class MarketOperationUtils {
|
||||
sourceFlags: SOURCE_FLAGS[ERC20BridgeSource.MultiHop],
|
||||
marketSideLiquidity,
|
||||
adjustedRate: bestTwoHopRate,
|
||||
takerTokenToEthRate,
|
||||
makerTokenToEthRate,
|
||||
unoptimizedPath,
|
||||
takerAmountPerEth,
|
||||
makerAmountPerEth,
|
||||
};
|
||||
}
|
||||
|
||||
@ -557,8 +563,9 @@ export class MarketOperationUtils {
|
||||
sourceFlags: collapsedPath.sourceFlags,
|
||||
marketSideLiquidity,
|
||||
adjustedRate: optimalPathRate,
|
||||
takerTokenToEthRate,
|
||||
makerTokenToEthRate,
|
||||
unoptimizedPath,
|
||||
takerAmountPerEth,
|
||||
makerAmountPerEth,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ export function getBestTwoHopQuote(
|
||||
feeSchedule?: FeeSchedule,
|
||||
exchangeProxyOverhead?: ExchangeProxyOverhead,
|
||||
): { quote: DexSample<MultiHopFillData> | undefined; adjustedRate: BigNumber } {
|
||||
const { side, inputAmount, ethToOutputRate, quotes } = marketSideLiquidity;
|
||||
const { side, inputAmount, outputAmountPerEth, quotes } = marketSideLiquidity;
|
||||
const { twoHopQuotes } = quotes;
|
||||
// Ensure the expected data we require exists. In the case where all hops reverted
|
||||
// or there were no sources included that allowed for multi hop,
|
||||
@ -57,7 +57,7 @@ export function getBestTwoHopQuote(
|
||||
}
|
||||
const best = filteredQuotes
|
||||
.map(quote =>
|
||||
getTwoHopAdjustedRate(side, quote, inputAmount, ethToOutputRate, feeSchedule, exchangeProxyOverhead),
|
||||
getTwoHopAdjustedRate(side, quote, inputAmount, outputAmountPerEth, feeSchedule, exchangeProxyOverhead),
|
||||
)
|
||||
.reduce(
|
||||
(prev, curr, i) =>
|
||||
@ -67,7 +67,7 @@ export function getBestTwoHopQuote(
|
||||
side,
|
||||
filteredQuotes[0],
|
||||
inputAmount,
|
||||
ethToOutputRate,
|
||||
outputAmountPerEth,
|
||||
feeSchedule,
|
||||
exchangeProxyOverhead,
|
||||
),
|
||||
|
@ -22,14 +22,14 @@ export interface PathSize {
|
||||
}
|
||||
|
||||
export interface PathPenaltyOpts {
|
||||
ethToOutputRate: BigNumber;
|
||||
ethToInputRate: BigNumber;
|
||||
outputAmountPerEth: BigNumber;
|
||||
inputAmountPerEth: BigNumber;
|
||||
exchangeProxyOverhead: ExchangeProxyOverhead;
|
||||
}
|
||||
|
||||
export const DEFAULT_PATH_PENALTY_OPTS: PathPenaltyOpts = {
|
||||
ethToOutputRate: ZERO_AMOUNT,
|
||||
ethToInputRate: ZERO_AMOUNT,
|
||||
outputAmountPerEth: ZERO_AMOUNT,
|
||||
inputAmountPerEth: ZERO_AMOUNT,
|
||||
exchangeProxyOverhead: () => ZERO_AMOUNT,
|
||||
};
|
||||
|
||||
@ -131,11 +131,11 @@ export class Path {
|
||||
|
||||
public adjustedSize(): PathSize {
|
||||
const { input, output } = this._adjustedSize;
|
||||
const { exchangeProxyOverhead, ethToOutputRate, ethToInputRate } = this.pathPenaltyOpts;
|
||||
const { exchangeProxyOverhead, outputAmountPerEth, inputAmountPerEth } = this.pathPenaltyOpts;
|
||||
const gasOverhead = exchangeProxyOverhead(this.sourceFlags);
|
||||
const pathPenalty = !ethToOutputRate.isZero()
|
||||
? ethToOutputRate.times(gasOverhead)
|
||||
: ethToInputRate.times(gasOverhead).times(output.dividedToIntegerBy(input));
|
||||
const pathPenalty = !outputAmountPerEth.isZero()
|
||||
? outputAmountPerEth.times(gasOverhead)
|
||||
: inputAmountPerEth.times(gasOverhead).times(output.dividedToIntegerBy(input));
|
||||
return {
|
||||
input,
|
||||
output: this.side === MarketOperation.Sell ? output.minus(pathPenalty) : output.plus(pathPenalty),
|
||||
|
@ -13,7 +13,7 @@ export function getTwoHopAdjustedRate(
|
||||
side: MarketOperation,
|
||||
twoHopQuote: DexSample<MultiHopFillData>,
|
||||
targetInput: BigNumber,
|
||||
ethToOutputRate: BigNumber,
|
||||
outputAmountPerEth: BigNumber,
|
||||
fees: FeeSchedule = {},
|
||||
exchangeProxyOverhead: ExchangeProxyOverhead = () => ZERO_AMOUNT,
|
||||
): BigNumber {
|
||||
@ -21,7 +21,7 @@ export function getTwoHopAdjustedRate(
|
||||
if (input.isLessThan(targetInput) || output.isZero()) {
|
||||
return ZERO_AMOUNT;
|
||||
}
|
||||
const penalty = ethToOutputRate.times(
|
||||
const penalty = outputAmountPerEth.times(
|
||||
exchangeProxyOverhead(SOURCE_FLAGS.MultiHop).plus(fees[ERC20BridgeSource.MultiHop]!(fillData)),
|
||||
);
|
||||
const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
|
||||
|
@ -11,6 +11,7 @@ import { NativeOrderWithFillableAmounts, RfqtFirmQuoteValidator, RfqtRequestOpts
|
||||
import { QuoteRequestor } from '../../utils/quote_requestor';
|
||||
import { QuoteReport } from '../quote_report_generator';
|
||||
|
||||
import { CollapsedPath } from './path';
|
||||
import { SourceFilters } from './source_filters';
|
||||
|
||||
/**
|
||||
@ -374,8 +375,9 @@ export interface OptimizerResult {
|
||||
liquidityDelivered: CollapsedFill[] | DexSample<MultiHopFillData>;
|
||||
marketSideLiquidity: MarketSideLiquidity;
|
||||
adjustedRate: BigNumber;
|
||||
takerTokenToEthRate: BigNumber;
|
||||
makerTokenToEthRate: BigNumber;
|
||||
unoptimizedPath?: CollapsedPath;
|
||||
takerAmountPerEth: BigNumber;
|
||||
makerAmountPerEth: BigNumber;
|
||||
}
|
||||
|
||||
export interface OptimizerResultWithReport extends OptimizerResult {
|
||||
@ -396,8 +398,8 @@ export interface MarketSideLiquidity {
|
||||
inputAmount: BigNumber;
|
||||
inputToken: string;
|
||||
outputToken: string;
|
||||
ethToOutputRate: BigNumber;
|
||||
ethToInputRate: BigNumber;
|
||||
outputAmountPerEth: BigNumber;
|
||||
inputAmountPerEth: BigNumber;
|
||||
quoteSourceFilters: SourceFilters;
|
||||
makerTokenDecimals: number;
|
||||
takerTokenDecimals: number;
|
||||
|
@ -49,8 +49,8 @@ const exchangeProxyOverhead = (sourceFlags: number) => {
|
||||
|
||||
const buyMarketSideLiquidity: MarketSideLiquidity = {
|
||||
// needed params
|
||||
ethToOutputRate: new BigNumber(500),
|
||||
ethToInputRate: new BigNumber(1),
|
||||
outputAmountPerEth: new BigNumber(500),
|
||||
inputAmountPerEth: new BigNumber(1),
|
||||
side: MarketOperation.Buy,
|
||||
makerTokenDecimals: 18,
|
||||
takerTokenDecimals: 18,
|
||||
@ -70,8 +70,8 @@ const buyMarketSideLiquidity: MarketSideLiquidity = {
|
||||
|
||||
const sellMarketSideLiquidity: MarketSideLiquidity = {
|
||||
// needed params
|
||||
ethToOutputRate: new BigNumber(500),
|
||||
ethToInputRate: new BigNumber(1),
|
||||
outputAmountPerEth: new BigNumber(500),
|
||||
inputAmountPerEth: new BigNumber(1),
|
||||
side: MarketOperation.Sell,
|
||||
makerTokenDecimals: 18,
|
||||
takerTokenDecimals: 18,
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
decodeAffiliateFeeTransformerData,
|
||||
decodeFillQuoteTransformerData,
|
||||
decodePayTakerTransformerData,
|
||||
decodePositiveSlippageFeeTransformerData,
|
||||
decodeWethTransformerData,
|
||||
ETH_TOKEN_ADDRESS,
|
||||
FillQuoteTransformerLimitOrderInfo,
|
||||
@ -17,9 +18,9 @@ import * as chai from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
import 'mocha';
|
||||
|
||||
import { constants } from '../src/constants';
|
||||
import { constants, POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS } from '../src/constants';
|
||||
import { ExchangeProxySwapQuoteConsumer } from '../src/quote_consumers/exchange_proxy_swap_quote_consumer';
|
||||
import { MarketBuySwapQuote, MarketOperation, MarketSellSwapQuote } from '../src/types';
|
||||
import { AffiliateFeeType, MarketBuySwapQuote, MarketOperation, MarketSellSwapQuote } from '../src/types';
|
||||
import {
|
||||
ERC20BridgeSource,
|
||||
OptimizedLimitOrder,
|
||||
@ -53,6 +54,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
|
||||
payTakerTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 2),
|
||||
fillQuoteTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 3),
|
||||
affiliateFeeTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 4),
|
||||
positiveSlippageFeeTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 5),
|
||||
},
|
||||
};
|
||||
let consumer: ExchangeProxySwapQuoteConsumer;
|
||||
@ -137,11 +139,11 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
|
||||
protocolFeeInWeiAmount: getRandomAmount(),
|
||||
feeTakerTokenAmount: getRandomAmount(),
|
||||
},
|
||||
makerAmountPerEth: getRandomInteger(1, 1e9),
|
||||
takerAmountPerEth: getRandomInteger(1, 1e9),
|
||||
...(side === MarketOperation.Buy
|
||||
? { type: MarketOperation.Buy, makerTokenFillAmount }
|
||||
: { type: MarketOperation.Sell, takerTokenFillAmount }),
|
||||
takerTokenToEthRate: getRandomAmount(),
|
||||
makerTokenToEthRate: getRandomAmount(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -336,6 +338,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
|
||||
recipient: randomAddress(),
|
||||
buyTokenFeeAmount: getRandomAmount(),
|
||||
sellTokenFeeAmount: ZERO_AMOUNT,
|
||||
feeType: AffiliateFeeType.PercentageFee,
|
||||
};
|
||||
const callInfo = await consumer.getCalldataOrThrowAsync(quote, {
|
||||
extensionContractOpts: { affiliateFee },
|
||||
@ -349,12 +352,42 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
|
||||
{ token: MAKER_TOKEN, amount: affiliateFee.buyTokenFeeAmount, recipient: affiliateFee.recipient },
|
||||
]);
|
||||
});
|
||||
it('Appends a positive slippage affiliate fee transformer after the fill if the positive slippage fee feeType is specified', async () => {
|
||||
const quote = getRandomSellQuote();
|
||||
const affiliateFee = {
|
||||
recipient: randomAddress(),
|
||||
buyTokenFeeAmount: ZERO_AMOUNT,
|
||||
sellTokenFeeAmount: ZERO_AMOUNT,
|
||||
feeType: AffiliateFeeType.PositiveSlippageFee,
|
||||
};
|
||||
const callInfo = await consumer.getCalldataOrThrowAsync(quote, {
|
||||
extensionContractOpts: { affiliateFee },
|
||||
});
|
||||
const callArgs = transformERC20Encoder.decode(callInfo.calldataHexString) as TransformERC20Args;
|
||||
expect(callArgs.transformations[1].deploymentNonce.toNumber()).to.eq(
|
||||
consumer.transformerNonces.positiveSlippageFeeTransformer,
|
||||
);
|
||||
const positiveSlippageFeeTransformerData = decodePositiveSlippageFeeTransformerData(
|
||||
callArgs.transformations[1].data,
|
||||
);
|
||||
const bestCaseAmount = quote.bestCaseQuoteInfo.makerAmount.plus(
|
||||
POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS.multipliedBy(quote.gasPrice).multipliedBy(
|
||||
quote.makerAmountPerEth,
|
||||
),
|
||||
);
|
||||
expect(positiveSlippageFeeTransformerData).to.deep.equal({
|
||||
token: MAKER_TOKEN,
|
||||
bestCaseAmount,
|
||||
recipient: affiliateFee.recipient,
|
||||
});
|
||||
});
|
||||
it('Throws if a sell token affiliate fee is provided', async () => {
|
||||
const quote = getRandomSellQuote();
|
||||
const affiliateFee = {
|
||||
recipient: randomAddress(),
|
||||
buyTokenFeeAmount: ZERO_AMOUNT,
|
||||
sellTokenFeeAmount: getRandomAmount(),
|
||||
feeType: AffiliateFeeType.PercentageFee,
|
||||
};
|
||||
expect(
|
||||
consumer.getCalldataOrThrowAsync(quote, {
|
||||
|
@ -756,8 +756,8 @@ describe('MarketOperationUtils tests', () => {
|
||||
inputAmount: Web3Wrapper.toBaseUnitAmount(1, 18),
|
||||
inputToken: MAKER_TOKEN,
|
||||
outputToken: TAKER_TOKEN,
|
||||
ethToInputRate: Web3Wrapper.toBaseUnitAmount(1, 18),
|
||||
ethToOutputRate: Web3Wrapper.toBaseUnitAmount(1, 6),
|
||||
inputAmountPerEth: Web3Wrapper.toBaseUnitAmount(1, 18),
|
||||
outputAmountPerEth: Web3Wrapper.toBaseUnitAmount(1, 6),
|
||||
quoteSourceFilters: new SourceFilters(),
|
||||
makerTokenDecimals: 6,
|
||||
takerTokenDecimals: 18,
|
||||
@ -1787,7 +1787,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
|
||||
describe('createFills', () => {
|
||||
const takerAmount = new BigNumber(5000000);
|
||||
const ethToOutputRate = new BigNumber(0.5);
|
||||
const outputAmountPerEth = new BigNumber(0.5);
|
||||
// tslint:disable-next-line:no-object-literal-type-assertion
|
||||
const smallOrder: NativeOrderWithFillableAmounts = {
|
||||
order: {
|
||||
@ -1830,7 +1830,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
orders,
|
||||
dexQuotes: [],
|
||||
targetInput: takerAmount.minus(1),
|
||||
ethToOutputRate,
|
||||
outputAmountPerEth,
|
||||
feeSchedule,
|
||||
});
|
||||
expect((path[0][0].fillData as NativeFillData).order.maker).to.eq(smallOrder.order.maker);
|
||||
@ -1843,7 +1843,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
orders,
|
||||
dexQuotes: [],
|
||||
targetInput: POSITIVE_INF,
|
||||
ethToOutputRate,
|
||||
outputAmountPerEth,
|
||||
feeSchedule,
|
||||
});
|
||||
expect((path[0][0].fillData as NativeFillData).order.maker).to.eq(largeOrder.order.maker);
|
||||
|
@ -39,8 +39,8 @@ export async function getFullyFillableSwapQuoteWithNoFeesAsync(
|
||||
worstCaseQuoteInfo: quoteInfo,
|
||||
sourceBreakdown: breakdown,
|
||||
isTwoHop: false,
|
||||
takerTokenToEthRate: constants.ZERO_AMOUNT,
|
||||
makerTokenToEthRate: constants.ZERO_AMOUNT,
|
||||
takerAmountPerEth: constants.ZERO_AMOUNT,
|
||||
makerAmountPerEth: constants.ZERO_AMOUNT,
|
||||
makerTokenDecimals: 18,
|
||||
takerTokenDecimals: 18,
|
||||
};
|
||||
|
@ -5,6 +5,10 @@
|
||||
{
|
||||
"note": "Deploy new FQT",
|
||||
"pr": 155
|
||||
},
|
||||
{
|
||||
"note": "Deploy new `PositiveSlippageFeeTransformer`",
|
||||
"pr": 101
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -37,7 +37,8 @@
|
||||
"wethTransformer": "0xb2bc06a4efb20fc6553a69dbfa49b7be938034a7",
|
||||
"payTakerTransformer": "0x4638a7ebe75b911b995d0ec73a81e4f85f41f24e",
|
||||
"affiliateFeeTransformer": "0xda6d9fc5998f550a094585cf9171f0e8ee3ac59f",
|
||||
"fillQuoteTransformer": "0x227e767a9b7517681d1cb6b846aa9e541484c7ab"
|
||||
"fillQuoteTransformer": "0x227e767a9b7517681d1cb6b846aa9e541484c7ab",
|
||||
"positiveSlippageFeeTransformer": "0xa9416ce1dbde8d331210c07b5c253d94ee4cc3fd"
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
@ -78,7 +79,8 @@
|
||||
"wethTransformer": "0x05ad19aa3826e0609a19568ffbd1dfe86c6c7184",
|
||||
"payTakerTransformer": "0x6d0ebf2bcd9cc93ec553b60ad201943dcca4e291",
|
||||
"affiliateFeeTransformer": "0x6588256778ca4432fa43983ac685c45efb2379e2",
|
||||
"fillQuoteTransformer": "0x2088a820787ebbe937a0612ef024f1e1d65f9784"
|
||||
"fillQuoteTransformer": "0x2088a820787ebbe937a0612ef024f1e1d65f9784",
|
||||
"positiveSlippageFeeTransformer": "0x8b332f700fd37e71c5c5b26c4d78b5ca63dd33b2"
|
||||
}
|
||||
},
|
||||
"4": {
|
||||
@ -119,7 +121,8 @@
|
||||
"wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437",
|
||||
"payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6",
|
||||
"affiliateFeeTransformer": "0xa39b40642e8e00435857a0fe7d0655e08cc2217e",
|
||||
"fillQuoteTransformer": "0x3fb85e0c1e9e0ba4ba9a4072442a2540c0473db1"
|
||||
"fillQuoteTransformer": "0x3fb85e0c1e9e0ba4ba9a4072442a2540c0473db1",
|
||||
"positiveSlippageFeeTransformer": "0x0000000000000000000000000000000000000000"
|
||||
}
|
||||
},
|
||||
"42": {
|
||||
@ -160,7 +163,8 @@
|
||||
"wethTransformer": "0x9ce35b5ee9e710535e3988e3f8731d9ca9dba17d",
|
||||
"payTakerTransformer": "0x5a53e7b02a83aa9f60ccf4e424f0442c255bc977",
|
||||
"affiliateFeeTransformer": "0x870893920a96a28d4b63c0a7d06a521e3bd074b3",
|
||||
"fillQuoteTransformer": "0x8d2d732e5fe6d4d6d5e715200b84dfa69fb05478"
|
||||
"fillQuoteTransformer": "0x8d2d732e5fe6d4d6d5e715200b84dfa69fb05478",
|
||||
"positiveSlippageFeeTransformer": "0x0000000000000000000000000000000000000000"
|
||||
}
|
||||
},
|
||||
"1337": {
|
||||
@ -201,7 +205,8 @@
|
||||
"wethTransformer": "0x7209185959d7227fb77274e1e88151d7c4c368d3",
|
||||
"payTakerTransformer": "0x3f16ca81691dab9184cb4606c361d73c4fd2510a",
|
||||
"affiliateFeeTransformer": "0x99356167edba8fbdc36959e3f5d0c43d1ba9c6db",
|
||||
"fillQuoteTransformer": "0x45b3a72221e571017c0f0ec42189e11d149d0ace"
|
||||
"fillQuoteTransformer": "0x45b3a72221e571017c0f0ec42189e11d149d0ace",
|
||||
"positiveSlippageFeeTransformer": "0xdd66c23e07b4d6925b6089b5fe6fc9e62941afe8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ export interface ContractAddresses {
|
||||
payTakerTransformer: string;
|
||||
fillQuoteTransformer: string;
|
||||
affiliateFeeTransformer: string;
|
||||
positiveSlippageFeeTransformer: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,7 @@ import {
|
||||
FillQuoteTransformerContract,
|
||||
fullMigrateAsync as fullMigrateExchangeProxyAsync,
|
||||
PayTakerTransformerContract,
|
||||
PositiveSlippageFeeTransformerContract,
|
||||
WethTransformerContract,
|
||||
} from '@0x/contracts-zero-ex';
|
||||
import { Web3ProviderEngine } from '@0x/subproviders';
|
||||
@ -345,7 +346,12 @@ export async function runMigrationsAsync(
|
||||
bridgeAdapter.address,
|
||||
exchangeProxy.address,
|
||||
);
|
||||
|
||||
const positiveSlippageFeeTransformer = await PositiveSlippageFeeTransformerContract.deployFrom0xArtifactAsync(
|
||||
exchangeProxyArtifacts.PositiveSlippageFeeTransformer,
|
||||
provider,
|
||||
txDefaults,
|
||||
allArtifacts,
|
||||
);
|
||||
const contractAddresses = {
|
||||
erc20Proxy: erc20Proxy.address,
|
||||
erc721Proxy: erc721Proxy.address,
|
||||
@ -385,6 +391,7 @@ export async function runMigrationsAsync(
|
||||
payTakerTransformer: payTakerTransformer.address,
|
||||
fillQuoteTransformer: fillQuoteTransformer.address,
|
||||
affiliateFeeTransformer: affiliateFeeTransformer.address,
|
||||
positiveSlippageFeeTransformer: positiveSlippageFeeTransformer.address,
|
||||
},
|
||||
};
|
||||
return contractAddresses;
|
||||
|
@ -77,6 +77,9 @@ export {
|
||||
AffiliateFeeTransformerData,
|
||||
encodeAffiliateFeeTransformerData,
|
||||
decodeAffiliateFeeTransformerData,
|
||||
PositiveSlippageFeeTransformerData,
|
||||
encodePositiveSlippageFeeTransformerData,
|
||||
decodePositiveSlippageFeeTransformerData,
|
||||
findTransformerNonce,
|
||||
getTransformerAddress,
|
||||
} from './transformer_utils';
|
||||
|
@ -152,7 +152,7 @@ export function decodePayTakerTransformerData(encoded: string): PayTakerTransfor
|
||||
}
|
||||
|
||||
/**
|
||||
* ABI encoder for `PayTakerTransformer.TransformData`
|
||||
* ABI encoder for `affiliateFeetransformer.TransformData`
|
||||
*/
|
||||
export const affiliateFeeTransformerDataEncoder = AbiEncoder.create({
|
||||
name: 'data',
|
||||
@ -195,6 +195,42 @@ export function decodeAffiliateFeeTransformerData(encoded: string): AffiliateFee
|
||||
return affiliateFeeTransformerDataEncoder.decode(encoded);
|
||||
}
|
||||
|
||||
/**
|
||||
* ABI encoder for `PositiveSlippageFeeTransformer.TransformData`
|
||||
*/
|
||||
export const positiveSlippageFeeTransformerDataEncoder = AbiEncoder.create({
|
||||
name: 'data',
|
||||
type: 'tuple',
|
||||
components: [
|
||||
{ name: 'token', type: 'address' },
|
||||
{ name: 'bestCaseAmount', type: 'uint256' },
|
||||
{ name: 'recipient', type: 'address' },
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* `PositiveSlippageFeeTransformer.TransformData`
|
||||
*/
|
||||
export interface PositiveSlippageFeeTransformerData {
|
||||
token: string;
|
||||
bestCaseAmount: BigNumber;
|
||||
recipient: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ABI-encode a `PositiveSlippageFeeTransformer.TransformData` type.
|
||||
*/
|
||||
export function encodePositiveSlippageFeeTransformerData(data: PositiveSlippageFeeTransformerData): string {
|
||||
return positiveSlippageFeeTransformerDataEncoder.encode(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* ABI-decode a `PositiveSlippageFeeTransformer.TransformData` type.
|
||||
*/
|
||||
export function decodePositiveSlippageFeeTransformerData(encoded: string): PositiveSlippageFeeTransformerData {
|
||||
return positiveSlippageFeeTransformerDataEncoder.decode(encoded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the nonce for a transformer given its deployer.
|
||||
* If `deployer` is the null address, zero will always be returned.
|
||||
|
@ -242,7 +242,7 @@ export function decodePayTakerTransformerData(encoded: string): PayTakerTransfor
|
||||
}
|
||||
|
||||
/**
|
||||
* ABI encoder for `PayTakerTransformer.TransformData`
|
||||
* ABI encoder for `affiliateFeetransformer.TransformData`
|
||||
*/
|
||||
export const affiliateFeeTransformerDataEncoder = AbiEncoder.create({
|
||||
name: 'data',
|
||||
@ -317,3 +317,39 @@ export function getTransformerAddress(deployer: string, nonce: number): string {
|
||||
ethjs.rlphash([deployer, nonce] as any).slice(12),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* ABI encoder for `PositiveSlippageFeeTransformer.TransformData`
|
||||
*/
|
||||
export const positiveSlippageFeeTransformerDataEncoder = AbiEncoder.create({
|
||||
name: 'data',
|
||||
type: 'tuple',
|
||||
components: [
|
||||
{ name: 'token', type: 'address' },
|
||||
{ name: 'bestCaseAmount', type: 'uint256' },
|
||||
{ name: 'recipient', type: 'address' },
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* `PositiveSlippageFeeTransformer.TransformData`
|
||||
*/
|
||||
export interface PositiveSlippageFeeTransformerData {
|
||||
token: string;
|
||||
bestCaseAmount: BigNumber;
|
||||
recipient: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ABI-encode a `PositiveSlippageFeeTransformer.TransformData` type.
|
||||
*/
|
||||
export function encodePositiveSlippageFeeTransformerData(data: PositiveSlippageFeeTransformerData): string {
|
||||
return positiveSlippageFeeTransformerDataEncoder.encode(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* ABI-decode a `PositiveSlippageFeeTransformer.TransformData` type.
|
||||
*/
|
||||
export function decodePositiveSlippageFeeTransformerData(encoded: string): PositiveSlippageFeeTransformerData {
|
||||
return positiveSlippageFeeTransformerDataEncoder.decode(encoded);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user