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",
|
"note": "refund ETH with no gas limit in FQT",
|
||||||
"pr": 155
|
"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"
|
"rollback": "node ./lib/scripts/rollback.js"
|
||||||
},
|
},
|
||||||
"config": {
|
"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: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": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -29,6 +29,7 @@ import * as MetaTransactionsFeature from '../generated-artifacts/MetaTransaction
|
|||||||
import * as NativeOrdersFeature from '../generated-artifacts/NativeOrdersFeature.json';
|
import * as NativeOrdersFeature from '../generated-artifacts/NativeOrdersFeature.json';
|
||||||
import * as OwnableFeature from '../generated-artifacts/OwnableFeature.json';
|
import * as OwnableFeature from '../generated-artifacts/OwnableFeature.json';
|
||||||
import * as PayTakerTransformer from '../generated-artifacts/PayTakerTransformer.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 SimpleFunctionRegistryFeature from '../generated-artifacts/SimpleFunctionRegistryFeature.json';
|
||||||
import * as TokenSpenderFeature from '../generated-artifacts/TokenSpenderFeature.json';
|
import * as TokenSpenderFeature from '../generated-artifacts/TokenSpenderFeature.json';
|
||||||
import * as TransformERC20Feature from '../generated-artifacts/TransformERC20Feature.json';
|
import * as TransformERC20Feature from '../generated-artifacts/TransformERC20Feature.json';
|
||||||
@ -48,6 +49,7 @@ export const artifacts = {
|
|||||||
ITransformERC20Feature: ITransformERC20Feature as ContractArtifact,
|
ITransformERC20Feature: ITransformERC20Feature as ContractArtifact,
|
||||||
FillQuoteTransformer: FillQuoteTransformer as ContractArtifact,
|
FillQuoteTransformer: FillQuoteTransformer as ContractArtifact,
|
||||||
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
|
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
|
||||||
|
PositiveSlippageFeeTransformer: PositiveSlippageFeeTransformer as ContractArtifact,
|
||||||
WethTransformer: WethTransformer as ContractArtifact,
|
WethTransformer: WethTransformer as ContractArtifact,
|
||||||
OwnableFeature: OwnableFeature as ContractArtifact,
|
OwnableFeature: OwnableFeature as ContractArtifact,
|
||||||
SimpleFunctionRegistryFeature: SimpleFunctionRegistryFeature as ContractArtifact,
|
SimpleFunctionRegistryFeature: SimpleFunctionRegistryFeature as ContractArtifact,
|
||||||
|
@ -46,6 +46,7 @@ export {
|
|||||||
IZeroExContract,
|
IZeroExContract,
|
||||||
LogMetadataTransformerContract,
|
LogMetadataTransformerContract,
|
||||||
PayTakerTransformerContract,
|
PayTakerTransformerContract,
|
||||||
|
PositiveSlippageFeeTransformerContract,
|
||||||
WethTransformerContract,
|
WethTransformerContract,
|
||||||
ZeroExContract,
|
ZeroExContract,
|
||||||
} from './wrappers';
|
} from './wrappers';
|
||||||
|
@ -27,6 +27,7 @@ export * from '../generated-wrappers/meta_transactions_feature';
|
|||||||
export * from '../generated-wrappers/native_orders_feature';
|
export * from '../generated-wrappers/native_orders_feature';
|
||||||
export * from '../generated-wrappers/ownable_feature';
|
export * from '../generated-wrappers/ownable_feature';
|
||||||
export * from '../generated-wrappers/pay_taker_transformer';
|
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/simple_function_registry_feature';
|
||||||
export * from '../generated-wrappers/token_spender_feature';
|
export * from '../generated-wrappers/token_spender_feature';
|
||||||
export * from '../generated-wrappers/transform_erc20_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 OwnableFeature from '../test/generated-artifacts/OwnableFeature.json';
|
||||||
import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json';
|
import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json';
|
||||||
import * as PermissionlessTransformerDeployer from '../test/generated-artifacts/PermissionlessTransformerDeployer.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 SimpleFunctionRegistryFeature from '../test/generated-artifacts/SimpleFunctionRegistryFeature.json';
|
||||||
import * as TestBridge from '../test/generated-artifacts/TestBridge.json';
|
import * as TestBridge from '../test/generated-artifacts/TestBridge.json';
|
||||||
import * as TestCallTarget from '../test/generated-artifacts/TestCallTarget.json';
|
import * as TestCallTarget from '../test/generated-artifacts/TestCallTarget.json';
|
||||||
@ -209,6 +210,7 @@ export const artifacts = {
|
|||||||
LibERC20Transformer: LibERC20Transformer as ContractArtifact,
|
LibERC20Transformer: LibERC20Transformer as ContractArtifact,
|
||||||
LogMetadataTransformer: LogMetadataTransformer as ContractArtifact,
|
LogMetadataTransformer: LogMetadataTransformer as ContractArtifact,
|
||||||
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
|
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
|
||||||
|
PositiveSlippageFeeTransformer: PositiveSlippageFeeTransformer as ContractArtifact,
|
||||||
Transformer: Transformer as ContractArtifact,
|
Transformer: Transformer as ContractArtifact,
|
||||||
WethTransformer: WethTransformer as ContractArtifact,
|
WethTransformer: WethTransformer as ContractArtifact,
|
||||||
BridgeAdapter: BridgeAdapter 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/ownable_feature';
|
||||||
export * from '../test/generated-wrappers/pay_taker_transformer';
|
export * from '../test/generated-wrappers/pay_taker_transformer';
|
||||||
export * from '../test/generated-wrappers/permissionless_transformer_deployer';
|
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/simple_function_registry_feature';
|
||||||
export * from '../test/generated-wrappers/test_bridge';
|
export * from '../test/generated-wrappers/test_bridge';
|
||||||
export * from '../test/generated-wrappers/test_call_target';
|
export * from '../test/generated-wrappers/test_call_target';
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
"generated-artifacts/NativeOrdersFeature.json",
|
"generated-artifacts/NativeOrdersFeature.json",
|
||||||
"generated-artifacts/OwnableFeature.json",
|
"generated-artifacts/OwnableFeature.json",
|
||||||
"generated-artifacts/PayTakerTransformer.json",
|
"generated-artifacts/PayTakerTransformer.json",
|
||||||
|
"generated-artifacts/PositiveSlippageFeeTransformer.json",
|
||||||
"generated-artifacts/SimpleFunctionRegistryFeature.json",
|
"generated-artifacts/SimpleFunctionRegistryFeature.json",
|
||||||
"generated-artifacts/TokenSpenderFeature.json",
|
"generated-artifacts/TokenSpenderFeature.json",
|
||||||
"generated-artifacts/TransformERC20Feature.json",
|
"generated-artifacts/TransformERC20Feature.json",
|
||||||
@ -119,6 +120,7 @@
|
|||||||
"test/generated-artifacts/OwnableFeature.json",
|
"test/generated-artifacts/OwnableFeature.json",
|
||||||
"test/generated-artifacts/PayTakerTransformer.json",
|
"test/generated-artifacts/PayTakerTransformer.json",
|
||||||
"test/generated-artifacts/PermissionlessTransformerDeployer.json",
|
"test/generated-artifacts/PermissionlessTransformerDeployer.json",
|
||||||
|
"test/generated-artifacts/PositiveSlippageFeeTransformer.json",
|
||||||
"test/generated-artifacts/SimpleFunctionRegistryFeature.json",
|
"test/generated-artifacts/SimpleFunctionRegistryFeature.json",
|
||||||
"test/generated-artifacts/TestBridge.json",
|
"test/generated-artifacts/TestBridge.json",
|
||||||
"test/generated-artifacts/TestCallTarget.json",
|
"test/generated-artifacts/TestCallTarget.json",
|
||||||
|
@ -49,6 +49,10 @@
|
|||||||
{
|
{
|
||||||
"note": "Add an alternative RFQ market making implementation",
|
"note": "Add an alternative RFQ market making implementation",
|
||||||
"pr": 139
|
"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 { BigNumber, logUtils } from '@0x/utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AffiliateFeeType,
|
||||||
ExchangeProxyContractOpts,
|
ExchangeProxyContractOpts,
|
||||||
LogFunction,
|
LogFunction,
|
||||||
OrderPrunerOpts,
|
OrderPrunerOpts,
|
||||||
@ -12,7 +13,11 @@ import {
|
|||||||
SwapQuoteRequestOpts,
|
SwapQuoteRequestOpts,
|
||||||
SwapQuoterOpts,
|
SwapQuoterOpts,
|
||||||
} from './types';
|
} 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 ETH_GAS_STATION_API_URL = 'https://ethgasstation.info/api/ethgasAPI.json';
|
||||||
const NULL_BYTES = '0x';
|
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
|
// default 50% buffer for selecting native orders to be aggregated with other sources
|
||||||
const MARKET_UTILS_AMOUNT_BUFFER_PERCENTAGE = 0.5;
|
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 = {
|
const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
|
||||||
chainId: ChainId.Mainnet,
|
chainId: ChainId.Mainnet,
|
||||||
orderRefreshIntervalMs: 10000, // 10 seconds
|
orderRefreshIntervalMs: 10000, // 10 seconds
|
||||||
@ -49,12 +53,14 @@ const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
|
|||||||
takerApiKeyWhitelist: [],
|
takerApiKeyWhitelist: [],
|
||||||
makerAssetOfferings: {},
|
makerAssetOfferings: {},
|
||||||
},
|
},
|
||||||
|
tokenAdjacencyGraph: DEFAULT_TOKEN_ADJACENCY_GRAPH,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS: ExchangeProxyContractOpts = {
|
const DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS: ExchangeProxyContractOpts = {
|
||||||
isFromETH: false,
|
isFromETH: false,
|
||||||
isToETH: false,
|
isToETH: false,
|
||||||
affiliateFee: {
|
affiliateFee: {
|
||||||
|
feeType: AffiliateFeeType.None,
|
||||||
recipient: NULL_ADDRESS,
|
recipient: NULL_ADDRESS,
|
||||||
buyTokenFeeAmount: ZERO_AMOUNT,
|
buyTokenFeeAmount: ZERO_AMOUNT,
|
||||||
sellTokenFeeAmount: 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 { 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 = {
|
export const constants = {
|
||||||
ETH_GAS_STATION_API_URL,
|
ETH_GAS_STATION_API_URL,
|
||||||
PROTOCOL_FEE_MULTIPLIER,
|
PROTOCOL_FEE_MULTIPLIER,
|
||||||
|
POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS,
|
||||||
NULL_BYTES,
|
NULL_BYTES,
|
||||||
ZERO_AMOUNT,
|
ZERO_AMOUNT,
|
||||||
NULL_ADDRESS,
|
NULL_ADDRESS,
|
||||||
|
@ -74,9 +74,10 @@ export { InsufficientAssetLiquidityError } from './errors';
|
|||||||
export { SwapQuoteConsumer } from './quote_consumers/swap_quote_consumer';
|
export { SwapQuoteConsumer } from './quote_consumers/swap_quote_consumer';
|
||||||
export { SwapQuoter, Orderbook } from './swap_quoter';
|
export { SwapQuoter, Orderbook } from './swap_quoter';
|
||||||
export {
|
export {
|
||||||
AffiliateFee,
|
|
||||||
AltOffering,
|
AltOffering,
|
||||||
AltRfqtMakerAssetOfferings,
|
AltRfqtMakerAssetOfferings,
|
||||||
|
AffiliateFeeType,
|
||||||
|
AffiliateFeeAmount,
|
||||||
AssetSwapperContractAddresses,
|
AssetSwapperContractAddresses,
|
||||||
CalldataInfo,
|
CalldataInfo,
|
||||||
ExchangeProxyContractOpts,
|
ExchangeProxyContractOpts,
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
encodeCurveLiquidityProviderData,
|
encodeCurveLiquidityProviderData,
|
||||||
encodeFillQuoteTransformerData,
|
encodeFillQuoteTransformerData,
|
||||||
encodePayTakerTransformerData,
|
encodePayTakerTransformerData,
|
||||||
|
encodePositiveSlippageFeeTransformerData,
|
||||||
encodeWethTransformerData,
|
encodeWethTransformerData,
|
||||||
ETH_TOKEN_ADDRESS,
|
ETH_TOKEN_ADDRESS,
|
||||||
FillQuoteTransformerData,
|
FillQuoteTransformerData,
|
||||||
@ -16,8 +17,9 @@ import { BigNumber, providerUtils } from '@0x/utils';
|
|||||||
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
|
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { constants } from '../constants';
|
import { constants, POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS } from '../constants';
|
||||||
import {
|
import {
|
||||||
|
AffiliateFeeType,
|
||||||
CalldataInfo,
|
CalldataInfo,
|
||||||
ExchangeProxyContractOpts,
|
ExchangeProxyContractOpts,
|
||||||
MarketBuySwapQuote,
|
MarketBuySwapQuote,
|
||||||
@ -59,6 +61,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
payTakerTransformer: number;
|
payTakerTransformer: number;
|
||||||
fillQuoteTransformer: number;
|
fillQuoteTransformer: number;
|
||||||
affiliateFeeTransformer: number;
|
affiliateFeeTransformer: number;
|
||||||
|
positiveSlippageFeeTransformer: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly _exchangeProxy: IZeroExContract;
|
private readonly _exchangeProxy: IZeroExContract;
|
||||||
@ -92,6 +95,10 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
contractAddresses.transformers.affiliateFeeTransformer,
|
contractAddresses.transformers.affiliateFeeTransformer,
|
||||||
contractAddresses.exchangeProxyTransformerDeployer,
|
contractAddresses.exchangeProxyTransformerDeployer,
|
||||||
),
|
),
|
||||||
|
positiveSlippageFeeTransformer: findTransformerNonce(
|
||||||
|
contractAddresses.transformers.positiveSlippageFeeTransformer,
|
||||||
|
contractAddresses.exchangeProxyTransformerDeployer,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +124,6 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
if (isFromETH) {
|
if (isFromETH) {
|
||||||
ethAmount = ethAmount.plus(sellAmount);
|
ethAmount = ethAmount.plus(sellAmount);
|
||||||
}
|
}
|
||||||
const { buyTokenFeeAmount, sellTokenFeeAmount, recipient: feeRecipient } = affiliateFee;
|
|
||||||
|
|
||||||
// VIP routes.
|
// VIP routes.
|
||||||
if (
|
if (
|
||||||
@ -144,7 +150,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
.getABIEncodedTransactionData(),
|
.getABIEncodedTransactionData(),
|
||||||
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
||||||
toAddress: this._exchangeProxy.address,
|
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(),
|
.getABIEncodedTransactionData(),
|
||||||
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
||||||
toAddress: this._exchangeProxy.address,
|
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(),
|
.getABIEncodedTransactionData(),
|
||||||
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
||||||
toAddress: this._exchangeProxy.address,
|
toAddress: this._exchangeProxy.address,
|
||||||
allowanceTarget: this.contractAddresses.exchangeProxyAllowanceTarget,
|
allowanceTarget: this._exchangeProxy.address,
|
||||||
|
gasOverhead: ZERO_AMOUNT,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,8 +271,34 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.positiveSlippageFeeTransformer,
|
||||||
|
data: encodePositiveSlippageFeeTransformerData({
|
||||||
|
token: isToETH ? ETH_TOKEN_ADDRESS : buyToken,
|
||||||
|
bestCaseAmount: BigNumber.max(bestCaseAmountWithSurplus, quote.bestCaseQuoteInfo.makerAmount),
|
||||||
|
recipient: feeRecipient,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
// 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.
|
// This transformer pays affiliate fees.
|
||||||
if (buyTokenFeeAmount.isGreaterThan(0) && feeRecipient !== NULL_ADDRESS) {
|
if (buyTokenFeeAmount.isGreaterThan(0)) {
|
||||||
transforms.push({
|
transforms.push({
|
||||||
deploymentNonce: this.transformerNonces.affiliateFeeTransformer,
|
deploymentNonce: this.transformerNonces.affiliateFeeTransformer,
|
||||||
data: encodeAffiliateFeeTransformerData({
|
data: encodeAffiliateFeeTransformerData({
|
||||||
@ -279,9 +314,10 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
// Adjust the minimum buy amount by the fee.
|
// Adjust the minimum buy amount by the fee.
|
||||||
minBuyAmount = BigNumber.max(0, minBuyAmount.minus(buyTokenFeeAmount));
|
minBuyAmount = BigNumber.max(0, minBuyAmount.minus(buyTokenFeeAmount));
|
||||||
}
|
}
|
||||||
if (sellTokenFeeAmount.isGreaterThan(0) && feeRecipient !== NULL_ADDRESS) {
|
if (sellTokenFeeAmount.isGreaterThan(0)) {
|
||||||
throw new Error('Affiliate fees denominated in sell token are not yet supported');
|
throw new Error('Affiliate fees denominated in sell token are not yet supported');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The final transformer will send all funds to the taker.
|
// The final transformer will send all funds to the taker.
|
||||||
transforms.push({
|
transforms.push({
|
||||||
@ -306,7 +342,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
calldataHexString,
|
calldataHexString,
|
||||||
ethAmount,
|
ethAmount,
|
||||||
toAddress: this._exchangeProxy.address,
|
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)) {
|
if (!opts.affiliateFee.buyTokenFeeAmount.eq(0) || !opts.affiliateFee.sellTokenFeeAmount.eq(0)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// Must not have a positive slippage fee.
|
||||||
|
if (opts.affiliateFee.feeType === AffiliateFeeType.PositiveSlippageFee) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// Must be a single order.
|
// Must be a single order.
|
||||||
if (quote.orders.length !== 1) {
|
if (quote.orders.length !== 1) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -454,7 +454,7 @@ function createSwapQuote(
|
|||||||
gasSchedule: FeeSchedule,
|
gasSchedule: FeeSchedule,
|
||||||
slippage: number,
|
slippage: number,
|
||||||
): SwapQuote {
|
): SwapQuote {
|
||||||
const { optimizedOrders, quoteReport, sourceFlags, takerTokenToEthRate, makerTokenToEthRate } = optimizerResult;
|
const { optimizedOrders, quoteReport, sourceFlags, takerAmountPerEth, makerAmountPerEth } = optimizerResult;
|
||||||
const isTwoHop = sourceFlags === SOURCE_FLAGS[ERC20BridgeSource.MultiHop];
|
const isTwoHop = sourceFlags === SOURCE_FLAGS[ERC20BridgeSource.MultiHop];
|
||||||
|
|
||||||
// Calculate quote info
|
// Calculate quote info
|
||||||
@ -474,8 +474,8 @@ function createSwapQuote(
|
|||||||
sourceBreakdown,
|
sourceBreakdown,
|
||||||
makerTokenDecimals,
|
makerTokenDecimals,
|
||||||
takerTokenDecimals,
|
takerTokenDecimals,
|
||||||
takerTokenToEthRate,
|
takerAmountPerEth,
|
||||||
makerTokenToEthRate,
|
makerAmountPerEth,
|
||||||
quoteReport,
|
quoteReport,
|
||||||
isTwoHop,
|
isTwoHop,
|
||||||
};
|
};
|
||||||
|
@ -54,12 +54,15 @@ export interface NativeOrderFillableAmountFields {
|
|||||||
* toAddress: The contract address to call.
|
* toAddress: The contract address to call.
|
||||||
* ethAmount: The eth amount in wei to send with the smart contract call.
|
* ethAmount: The eth amount in wei to send with the smart contract call.
|
||||||
* allowanceTarget: The address the taker should grant an allowance to.
|
* 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 {
|
export interface CalldataInfo {
|
||||||
calldataHexString: string;
|
calldataHexString: string;
|
||||||
toAddress: string;
|
toAddress: string;
|
||||||
ethAmount: BigNumber;
|
ethAmount: BigNumber;
|
||||||
allowanceTarget: string;
|
allowanceTarget: string;
|
||||||
|
gasOverhead: BigNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -98,7 +101,14 @@ export interface SwapQuoteExecutionOpts extends SwapQuoteGetOutputOpts {
|
|||||||
gasLimit?: number;
|
gasLimit?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AffiliateFee {
|
export enum AffiliateFeeType {
|
||||||
|
None,
|
||||||
|
PercentageFee,
|
||||||
|
PositiveSlippageFee,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AffiliateFeeAmount {
|
||||||
|
feeType: AffiliateFeeType;
|
||||||
recipient: string;
|
recipient: string;
|
||||||
buyTokenFeeAmount: BigNumber;
|
buyTokenFeeAmount: BigNumber;
|
||||||
sellTokenFeeAmount: BigNumber;
|
sellTokenFeeAmount: BigNumber;
|
||||||
@ -130,7 +140,7 @@ export enum ExchangeProxyRefundReceiver {
|
|||||||
export interface ExchangeProxyContractOpts {
|
export interface ExchangeProxyContractOpts {
|
||||||
isFromETH: boolean;
|
isFromETH: boolean;
|
||||||
isToETH: boolean;
|
isToETH: boolean;
|
||||||
affiliateFee: AffiliateFee;
|
affiliateFee: AffiliateFeeAmount;
|
||||||
refundReceiver: string | ExchangeProxyRefundReceiver;
|
refundReceiver: string | ExchangeProxyRefundReceiver;
|
||||||
isMetaTransaction: boolean;
|
isMetaTransaction: boolean;
|
||||||
shouldSellEntireBalance: boolean;
|
shouldSellEntireBalance: boolean;
|
||||||
@ -161,8 +171,8 @@ export interface SwapQuoteBase {
|
|||||||
isTwoHop: boolean;
|
isTwoHop: boolean;
|
||||||
makerTokenDecimals: number;
|
makerTokenDecimals: number;
|
||||||
takerTokenDecimals: number;
|
takerTokenDecimals: number;
|
||||||
takerTokenToEthRate: BigNumber;
|
takerAmountPerEth: BigNumber;
|
||||||
makerTokenToEthRate: 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)
|
// Calc native order fee penalty in output unit (maker units for sells, taker unit for buys)
|
||||||
const feePenalty = !marketSideLiquidity.ethToOutputRate.isZero()
|
const feePenalty = !marketSideLiquidity.outputAmountPerEth.isZero()
|
||||||
? marketSideLiquidity.ethToOutputRate.times(feeInEth)
|
? marketSideLiquidity.outputAmountPerEth.times(feeInEth)
|
||||||
: // if it's a sell, the input token is the taker token
|
: // if it's a sell, the input token is the taker token
|
||||||
marketSideLiquidity.ethToInputRate
|
marketSideLiquidity.inputAmountPerEth
|
||||||
.times(feeInEth)
|
.times(feeInEth)
|
||||||
.times(marketSideLiquidity.side === MarketOperation.Sell ? adjustedRate : adjustedRate.pow(-1));
|
.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 DEFAULT_FEE_SCHEDULE: Required<FeeSchedule> = { ...DEFAULT_GAS_SCHEDULE };
|
||||||
|
|
||||||
|
export const POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS = new BigNumber(20000);
|
||||||
|
|
||||||
// tslint:enable:custom-no-magic-numbers
|
// tslint:enable:custom-no-magic-numbers
|
||||||
|
|
||||||
export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
|
export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
|
||||||
|
@ -16,8 +16,8 @@ export function createFills(opts: {
|
|||||||
orders?: NativeOrderWithFillableAmounts[];
|
orders?: NativeOrderWithFillableAmounts[];
|
||||||
dexQuotes?: DexSample[][];
|
dexQuotes?: DexSample[][];
|
||||||
targetInput?: BigNumber;
|
targetInput?: BigNumber;
|
||||||
ethToOutputRate?: BigNumber;
|
outputAmountPerEth?: BigNumber;
|
||||||
ethToInputRate?: BigNumber;
|
inputAmountPerEth?: BigNumber;
|
||||||
excludedSources?: ERC20BridgeSource[];
|
excludedSources?: ERC20BridgeSource[];
|
||||||
feeSchedule?: FeeSchedule;
|
feeSchedule?: FeeSchedule;
|
||||||
}): Fill[][] {
|
}): Fill[][] {
|
||||||
@ -26,20 +26,20 @@ export function createFills(opts: {
|
|||||||
const feeSchedule = opts.feeSchedule || {};
|
const feeSchedule = opts.feeSchedule || {};
|
||||||
const orders = opts.orders || [];
|
const orders = opts.orders || [];
|
||||||
const dexQuotes = opts.dexQuotes || [];
|
const dexQuotes = opts.dexQuotes || [];
|
||||||
const ethToOutputRate = opts.ethToOutputRate || ZERO_AMOUNT;
|
const outputAmountPerEth = opts.outputAmountPerEth || ZERO_AMOUNT;
|
||||||
const ethToInputRate = opts.ethToInputRate || ZERO_AMOUNT;
|
const inputAmountPerEth = opts.inputAmountPerEth || ZERO_AMOUNT;
|
||||||
// Create native fills.
|
// Create native fills.
|
||||||
const nativeFills = nativeOrdersToFills(
|
const nativeFills = nativeOrdersToFills(
|
||||||
side,
|
side,
|
||||||
orders.filter(o => o.fillableTakerAmount.isGreaterThan(0)),
|
orders.filter(o => o.fillableTakerAmount.isGreaterThan(0)),
|
||||||
opts.targetInput,
|
opts.targetInput,
|
||||||
ethToOutputRate,
|
outputAmountPerEth,
|
||||||
ethToInputRate,
|
inputAmountPerEth,
|
||||||
feeSchedule,
|
feeSchedule,
|
||||||
);
|
);
|
||||||
// Create DEX fills.
|
// Create DEX fills.
|
||||||
const dexFills = dexQuotes.map(singleSourceSamples =>
|
const dexFills = dexQuotes.map(singleSourceSamples =>
|
||||||
dexSamplesToFills(side, singleSourceSamples, ethToOutputRate, ethToInputRate, feeSchedule),
|
dexSamplesToFills(side, singleSourceSamples, outputAmountPerEth, inputAmountPerEth, feeSchedule),
|
||||||
);
|
);
|
||||||
return [...dexFills, nativeFills]
|
return [...dexFills, nativeFills]
|
||||||
.map(p => clipFillsToInput(p, opts.targetInput))
|
.map(p => clipFillsToInput(p, opts.targetInput))
|
||||||
@ -75,8 +75,8 @@ function nativeOrdersToFills(
|
|||||||
side: MarketOperation,
|
side: MarketOperation,
|
||||||
orders: NativeOrderWithFillableAmounts[],
|
orders: NativeOrderWithFillableAmounts[],
|
||||||
targetInput: BigNumber = POSITIVE_INF,
|
targetInput: BigNumber = POSITIVE_INF,
|
||||||
ethToOutputRate: BigNumber,
|
outputAmountPerEth: BigNumber,
|
||||||
ethToInputRate: BigNumber,
|
inputAmountPerEth: BigNumber,
|
||||||
fees: FeeSchedule,
|
fees: FeeSchedule,
|
||||||
): Fill[] {
|
): Fill[] {
|
||||||
const sourcePathId = hexUtils.random();
|
const sourcePathId = hexUtils.random();
|
||||||
@ -89,9 +89,9 @@ function nativeOrdersToFills(
|
|||||||
const input = side === MarketOperation.Sell ? takerAmount : makerAmount;
|
const input = side === MarketOperation.Sell ? takerAmount : makerAmount;
|
||||||
const output = side === MarketOperation.Sell ? makerAmount : takerAmount;
|
const output = side === MarketOperation.Sell ? makerAmount : takerAmount;
|
||||||
const fee = fees[ERC20BridgeSource.Native] === undefined ? 0 : fees[ERC20BridgeSource.Native]!(o);
|
const fee = fees[ERC20BridgeSource.Native] === undefined ? 0 : fees[ERC20BridgeSource.Native]!(o);
|
||||||
const outputPenalty = !ethToOutputRate.isZero()
|
const outputPenalty = !outputAmountPerEth.isZero()
|
||||||
? ethToOutputRate.times(fee)
|
? outputAmountPerEth.times(fee)
|
||||||
: ethToInputRate.times(fee).times(output.dividedToIntegerBy(input));
|
: inputAmountPerEth.times(fee).times(output.dividedToIntegerBy(input));
|
||||||
// targetInput can be less than the order size
|
// targetInput can be less than the order size
|
||||||
// whilst the penalty is constant, it affects the adjusted output
|
// whilst the penalty is constant, it affects the adjusted output
|
||||||
// only up until the target has been exhausted.
|
// only up until the target has been exhausted.
|
||||||
@ -135,8 +135,8 @@ function nativeOrdersToFills(
|
|||||||
function dexSamplesToFills(
|
function dexSamplesToFills(
|
||||||
side: MarketOperation,
|
side: MarketOperation,
|
||||||
samples: DexSample[],
|
samples: DexSample[],
|
||||||
ethToOutputRate: BigNumber,
|
outputAmountPerEth: BigNumber,
|
||||||
ethToInputRate: BigNumber,
|
inputAmountPerEth: BigNumber,
|
||||||
fees: FeeSchedule,
|
fees: FeeSchedule,
|
||||||
): Fill[] {
|
): Fill[] {
|
||||||
const sourcePathId = hexUtils.random();
|
const sourcePathId = hexUtils.random();
|
||||||
@ -156,9 +156,9 @@ function dexSamplesToFills(
|
|||||||
let penalty = ZERO_AMOUNT;
|
let penalty = ZERO_AMOUNT;
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
// Only the first fill in a DEX path incurs a penalty.
|
// Only the first fill in a DEX path incurs a penalty.
|
||||||
penalty = !ethToOutputRate.isZero()
|
penalty = !outputAmountPerEth.isZero()
|
||||||
? ethToOutputRate.times(fee)
|
? outputAmountPerEth.times(fee)
|
||||||
: ethToInputRate.times(fee).times(output.dividedToIntegerBy(input));
|
: inputAmountPerEth.times(fee).times(output.dividedToIntegerBy(input));
|
||||||
}
|
}
|
||||||
const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
|
const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
|
||||||
|
|
||||||
|
@ -30,7 +30,8 @@ import {
|
|||||||
import { createFills } from './fills';
|
import { createFills } from './fills';
|
||||||
import { getBestTwoHopQuote } from './multihop_utils';
|
import { getBestTwoHopQuote } from './multihop_utils';
|
||||||
import { createOrdersFromTwoHopSample } from './orders';
|
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 { DexOrderSampler, getSampleAmounts } from './sampler';
|
||||||
import { SourceFilters } from './source_filters';
|
import { SourceFilters } from './source_filters';
|
||||||
import {
|
import {
|
||||||
@ -167,8 +168,8 @@ export class MarketOperationUtils {
|
|||||||
[
|
[
|
||||||
tokenDecimals,
|
tokenDecimals,
|
||||||
orderFillableTakerAmounts,
|
orderFillableTakerAmounts,
|
||||||
ethToMakerAssetRate,
|
outputAmountPerEth,
|
||||||
ethToTakerAssetRate,
|
inputAmountPerEth,
|
||||||
dexQuotes,
|
dexQuotes,
|
||||||
rawTwoHopQuotes,
|
rawTwoHopQuotes,
|
||||||
isTxOriginContract,
|
isTxOriginContract,
|
||||||
@ -195,8 +196,8 @@ export class MarketOperationUtils {
|
|||||||
inputAmount: takerAmount,
|
inputAmount: takerAmount,
|
||||||
inputToken: takerToken,
|
inputToken: takerToken,
|
||||||
outputToken: makerToken,
|
outputToken: makerToken,
|
||||||
ethToOutputRate: ethToMakerAssetRate,
|
outputAmountPerEth,
|
||||||
ethToInputRate: ethToTakerAssetRate,
|
inputAmountPerEth,
|
||||||
quoteSourceFilters,
|
quoteSourceFilters,
|
||||||
makerTokenDecimals: makerTokenDecimals.toNumber(),
|
makerTokenDecimals: makerTokenDecimals.toNumber(),
|
||||||
takerTokenDecimals: takerTokenDecimals.toNumber(),
|
takerTokenDecimals: takerTokenDecimals.toNumber(),
|
||||||
@ -321,8 +322,8 @@ export class MarketOperationUtils {
|
|||||||
inputAmount: makerAmount,
|
inputAmount: makerAmount,
|
||||||
inputToken: makerToken,
|
inputToken: makerToken,
|
||||||
outputToken: takerToken,
|
outputToken: takerToken,
|
||||||
ethToOutputRate: ethToTakerAssetRate,
|
outputAmountPerEth: ethToTakerAssetRate,
|
||||||
ethToInputRate: ethToMakerAssetRate,
|
inputAmountPerEth: ethToMakerAssetRate,
|
||||||
quoteSourceFilters,
|
quoteSourceFilters,
|
||||||
makerTokenDecimals: makerTokenDecimals.toNumber(),
|
makerTokenDecimals: makerTokenDecimals.toNumber(),
|
||||||
takerTokenDecimals: takerTokenDecimals.toNumber(),
|
takerTokenDecimals: takerTokenDecimals.toNumber(),
|
||||||
@ -392,7 +393,7 @@ export class MarketOperationUtils {
|
|||||||
const batchEthToTakerAssetRate = executeResults.splice(0, batchNativeOrders.length) as BigNumber[];
|
const batchEthToTakerAssetRate = executeResults.splice(0, batchNativeOrders.length) as BigNumber[];
|
||||||
const batchDexQuotes = executeResults.splice(0, batchNativeOrders.length) as DexSample[][][];
|
const batchDexQuotes = executeResults.splice(0, batchNativeOrders.length) as DexSample[][][];
|
||||||
const batchTokenDecimals = executeResults.splice(0, batchNativeOrders.length) as number[][];
|
const batchTokenDecimals = executeResults.splice(0, batchNativeOrders.length) as number[][];
|
||||||
const ethToInputRate = ZERO_AMOUNT;
|
const inputAmountPerEth = ZERO_AMOUNT;
|
||||||
|
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
batchNativeOrders.map(async (nativeOrders, i) => {
|
batchNativeOrders.map(async (nativeOrders, i) => {
|
||||||
@ -401,7 +402,7 @@ export class MarketOperationUtils {
|
|||||||
}
|
}
|
||||||
const { makerToken, takerToken } = nativeOrders[0].order;
|
const { makerToken, takerToken } = nativeOrders[0].order;
|
||||||
const orderFillableMakerAmounts = batchOrderFillableMakerAmounts[i];
|
const orderFillableMakerAmounts = batchOrderFillableMakerAmounts[i];
|
||||||
const ethToTakerAssetRate = batchEthToTakerAssetRate[i];
|
const outputAmountPerEth = batchEthToTakerAssetRate[i];
|
||||||
const dexQuotes = batchDexQuotes[i];
|
const dexQuotes = batchDexQuotes[i];
|
||||||
const makerAmount = makerAmounts[i];
|
const makerAmount = makerAmounts[i];
|
||||||
try {
|
try {
|
||||||
@ -411,8 +412,8 @@ export class MarketOperationUtils {
|
|||||||
inputToken: makerToken,
|
inputToken: makerToken,
|
||||||
outputToken: takerToken,
|
outputToken: takerToken,
|
||||||
inputAmount: makerAmount,
|
inputAmount: makerAmount,
|
||||||
ethToOutputRate: ethToTakerAssetRate,
|
outputAmountPerEth,
|
||||||
ethToInputRate,
|
inputAmountPerEth,
|
||||||
quoteSourceFilters,
|
quoteSourceFilters,
|
||||||
makerTokenDecimals: batchTokenDecimals[i][0],
|
makerTokenDecimals: batchTokenDecimals[i][0],
|
||||||
takerTokenDecimals: batchTokenDecimals[i][1],
|
takerTokenDecimals: batchTokenDecimals[i][1],
|
||||||
@ -455,8 +456,8 @@ export class MarketOperationUtils {
|
|||||||
side,
|
side,
|
||||||
inputAmount,
|
inputAmount,
|
||||||
quotes,
|
quotes,
|
||||||
ethToOutputRate,
|
outputAmountPerEth,
|
||||||
ethToInputRate,
|
inputAmountPerEth,
|
||||||
} = marketSideLiquidity;
|
} = marketSideLiquidity;
|
||||||
const { nativeOrders, rfqtIndicativeQuotes, dexQuotes } = quotes;
|
const { nativeOrders, rfqtIndicativeQuotes, dexQuotes } = quotes;
|
||||||
const maxFallbackSlippage = opts.maxFallbackSlippage || 0;
|
const maxFallbackSlippage = opts.maxFallbackSlippage || 0;
|
||||||
@ -489,25 +490,29 @@ export class MarketOperationUtils {
|
|||||||
orders: [...nativeOrders, ...augmentedRfqtIndicativeQuotes],
|
orders: [...nativeOrders, ...augmentedRfqtIndicativeQuotes],
|
||||||
dexQuotes,
|
dexQuotes,
|
||||||
targetInput: inputAmount,
|
targetInput: inputAmount,
|
||||||
ethToOutputRate,
|
outputAmountPerEth,
|
||||||
ethToInputRate,
|
inputAmountPerEth,
|
||||||
excludedSources: opts.excludedSources,
|
excludedSources: opts.excludedSources,
|
||||||
feeSchedule: opts.feeSchedule,
|
feeSchedule: opts.feeSchedule,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Find the optimal path.
|
// Find the optimal path.
|
||||||
const optimizerOpts = {
|
const penaltyOpts: PathPenaltyOpts = {
|
||||||
ethToOutputRate,
|
outputAmountPerEth,
|
||||||
ethToInputRate,
|
inputAmountPerEth,
|
||||||
exchangeProxyOverhead: opts.exchangeProxyOverhead || (() => ZERO_AMOUNT),
|
exchangeProxyOverhead: opts.exchangeProxyOverhead || (() => ZERO_AMOUNT),
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOTE: For sell quotes input is the taker asset and for buy quotes input is the maker asset
|
// 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 takerAmountPerEth = side === MarketOperation.Sell ? inputAmountPerEth : outputAmountPerEth;
|
||||||
const makerTokenToEthRate = side === MarketOperation.Sell ? ethToOutputRate : ethToInputRate;
|
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
|
// 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 optimalPathRate = optimalPath ? optimalPath.adjustedRate() : ZERO_AMOUNT;
|
||||||
|
|
||||||
const { adjustedRate: bestTwoHopRate, quote: bestTwoHopQuote } = getBestTwoHopQuote(
|
const { adjustedRate: bestTwoHopRate, quote: bestTwoHopQuote } = getBestTwoHopQuote(
|
||||||
@ -523,8 +528,9 @@ export class MarketOperationUtils {
|
|||||||
sourceFlags: SOURCE_FLAGS[ERC20BridgeSource.MultiHop],
|
sourceFlags: SOURCE_FLAGS[ERC20BridgeSource.MultiHop],
|
||||||
marketSideLiquidity,
|
marketSideLiquidity,
|
||||||
adjustedRate: bestTwoHopRate,
|
adjustedRate: bestTwoHopRate,
|
||||||
takerTokenToEthRate,
|
unoptimizedPath,
|
||||||
makerTokenToEthRate,
|
takerAmountPerEth,
|
||||||
|
makerAmountPerEth,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -557,8 +563,9 @@ export class MarketOperationUtils {
|
|||||||
sourceFlags: collapsedPath.sourceFlags,
|
sourceFlags: collapsedPath.sourceFlags,
|
||||||
marketSideLiquidity,
|
marketSideLiquidity,
|
||||||
adjustedRate: optimalPathRate,
|
adjustedRate: optimalPathRate,
|
||||||
takerTokenToEthRate,
|
unoptimizedPath,
|
||||||
makerTokenToEthRate,
|
takerAmountPerEth,
|
||||||
|
makerAmountPerEth,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ export function getBestTwoHopQuote(
|
|||||||
feeSchedule?: FeeSchedule,
|
feeSchedule?: FeeSchedule,
|
||||||
exchangeProxyOverhead?: ExchangeProxyOverhead,
|
exchangeProxyOverhead?: ExchangeProxyOverhead,
|
||||||
): { quote: DexSample<MultiHopFillData> | undefined; adjustedRate: BigNumber } {
|
): { quote: DexSample<MultiHopFillData> | undefined; adjustedRate: BigNumber } {
|
||||||
const { side, inputAmount, ethToOutputRate, quotes } = marketSideLiquidity;
|
const { side, inputAmount, outputAmountPerEth, quotes } = marketSideLiquidity;
|
||||||
const { twoHopQuotes } = quotes;
|
const { twoHopQuotes } = quotes;
|
||||||
// Ensure the expected data we require exists. In the case where all hops reverted
|
// 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,
|
// or there were no sources included that allowed for multi hop,
|
||||||
@ -57,7 +57,7 @@ export function getBestTwoHopQuote(
|
|||||||
}
|
}
|
||||||
const best = filteredQuotes
|
const best = filteredQuotes
|
||||||
.map(quote =>
|
.map(quote =>
|
||||||
getTwoHopAdjustedRate(side, quote, inputAmount, ethToOutputRate, feeSchedule, exchangeProxyOverhead),
|
getTwoHopAdjustedRate(side, quote, inputAmount, outputAmountPerEth, feeSchedule, exchangeProxyOverhead),
|
||||||
)
|
)
|
||||||
.reduce(
|
.reduce(
|
||||||
(prev, curr, i) =>
|
(prev, curr, i) =>
|
||||||
@ -67,7 +67,7 @@ export function getBestTwoHopQuote(
|
|||||||
side,
|
side,
|
||||||
filteredQuotes[0],
|
filteredQuotes[0],
|
||||||
inputAmount,
|
inputAmount,
|
||||||
ethToOutputRate,
|
outputAmountPerEth,
|
||||||
feeSchedule,
|
feeSchedule,
|
||||||
exchangeProxyOverhead,
|
exchangeProxyOverhead,
|
||||||
),
|
),
|
||||||
|
@ -22,14 +22,14 @@ export interface PathSize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PathPenaltyOpts {
|
export interface PathPenaltyOpts {
|
||||||
ethToOutputRate: BigNumber;
|
outputAmountPerEth: BigNumber;
|
||||||
ethToInputRate: BigNumber;
|
inputAmountPerEth: BigNumber;
|
||||||
exchangeProxyOverhead: ExchangeProxyOverhead;
|
exchangeProxyOverhead: ExchangeProxyOverhead;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_PATH_PENALTY_OPTS: PathPenaltyOpts = {
|
export const DEFAULT_PATH_PENALTY_OPTS: PathPenaltyOpts = {
|
||||||
ethToOutputRate: ZERO_AMOUNT,
|
outputAmountPerEth: ZERO_AMOUNT,
|
||||||
ethToInputRate: ZERO_AMOUNT,
|
inputAmountPerEth: ZERO_AMOUNT,
|
||||||
exchangeProxyOverhead: () => ZERO_AMOUNT,
|
exchangeProxyOverhead: () => ZERO_AMOUNT,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -131,11 +131,11 @@ export class Path {
|
|||||||
|
|
||||||
public adjustedSize(): PathSize {
|
public adjustedSize(): PathSize {
|
||||||
const { input, output } = this._adjustedSize;
|
const { input, output } = this._adjustedSize;
|
||||||
const { exchangeProxyOverhead, ethToOutputRate, ethToInputRate } = this.pathPenaltyOpts;
|
const { exchangeProxyOverhead, outputAmountPerEth, inputAmountPerEth } = this.pathPenaltyOpts;
|
||||||
const gasOverhead = exchangeProxyOverhead(this.sourceFlags);
|
const gasOverhead = exchangeProxyOverhead(this.sourceFlags);
|
||||||
const pathPenalty = !ethToOutputRate.isZero()
|
const pathPenalty = !outputAmountPerEth.isZero()
|
||||||
? ethToOutputRate.times(gasOverhead)
|
? outputAmountPerEth.times(gasOverhead)
|
||||||
: ethToInputRate.times(gasOverhead).times(output.dividedToIntegerBy(input));
|
: inputAmountPerEth.times(gasOverhead).times(output.dividedToIntegerBy(input));
|
||||||
return {
|
return {
|
||||||
input,
|
input,
|
||||||
output: this.side === MarketOperation.Sell ? output.minus(pathPenalty) : output.plus(pathPenalty),
|
output: this.side === MarketOperation.Sell ? output.minus(pathPenalty) : output.plus(pathPenalty),
|
||||||
|
@ -13,7 +13,7 @@ export function getTwoHopAdjustedRate(
|
|||||||
side: MarketOperation,
|
side: MarketOperation,
|
||||||
twoHopQuote: DexSample<MultiHopFillData>,
|
twoHopQuote: DexSample<MultiHopFillData>,
|
||||||
targetInput: BigNumber,
|
targetInput: BigNumber,
|
||||||
ethToOutputRate: BigNumber,
|
outputAmountPerEth: BigNumber,
|
||||||
fees: FeeSchedule = {},
|
fees: FeeSchedule = {},
|
||||||
exchangeProxyOverhead: ExchangeProxyOverhead = () => ZERO_AMOUNT,
|
exchangeProxyOverhead: ExchangeProxyOverhead = () => ZERO_AMOUNT,
|
||||||
): BigNumber {
|
): BigNumber {
|
||||||
@ -21,7 +21,7 @@ export function getTwoHopAdjustedRate(
|
|||||||
if (input.isLessThan(targetInput) || output.isZero()) {
|
if (input.isLessThan(targetInput) || output.isZero()) {
|
||||||
return ZERO_AMOUNT;
|
return ZERO_AMOUNT;
|
||||||
}
|
}
|
||||||
const penalty = ethToOutputRate.times(
|
const penalty = outputAmountPerEth.times(
|
||||||
exchangeProxyOverhead(SOURCE_FLAGS.MultiHop).plus(fees[ERC20BridgeSource.MultiHop]!(fillData)),
|
exchangeProxyOverhead(SOURCE_FLAGS.MultiHop).plus(fees[ERC20BridgeSource.MultiHop]!(fillData)),
|
||||||
);
|
);
|
||||||
const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
|
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 { QuoteRequestor } from '../../utils/quote_requestor';
|
||||||
import { QuoteReport } from '../quote_report_generator';
|
import { QuoteReport } from '../quote_report_generator';
|
||||||
|
|
||||||
|
import { CollapsedPath } from './path';
|
||||||
import { SourceFilters } from './source_filters';
|
import { SourceFilters } from './source_filters';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -374,8 +375,9 @@ export interface OptimizerResult {
|
|||||||
liquidityDelivered: CollapsedFill[] | DexSample<MultiHopFillData>;
|
liquidityDelivered: CollapsedFill[] | DexSample<MultiHopFillData>;
|
||||||
marketSideLiquidity: MarketSideLiquidity;
|
marketSideLiquidity: MarketSideLiquidity;
|
||||||
adjustedRate: BigNumber;
|
adjustedRate: BigNumber;
|
||||||
takerTokenToEthRate: BigNumber;
|
unoptimizedPath?: CollapsedPath;
|
||||||
makerTokenToEthRate: BigNumber;
|
takerAmountPerEth: BigNumber;
|
||||||
|
makerAmountPerEth: BigNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OptimizerResultWithReport extends OptimizerResult {
|
export interface OptimizerResultWithReport extends OptimizerResult {
|
||||||
@ -396,8 +398,8 @@ export interface MarketSideLiquidity {
|
|||||||
inputAmount: BigNumber;
|
inputAmount: BigNumber;
|
||||||
inputToken: string;
|
inputToken: string;
|
||||||
outputToken: string;
|
outputToken: string;
|
||||||
ethToOutputRate: BigNumber;
|
outputAmountPerEth: BigNumber;
|
||||||
ethToInputRate: BigNumber;
|
inputAmountPerEth: BigNumber;
|
||||||
quoteSourceFilters: SourceFilters;
|
quoteSourceFilters: SourceFilters;
|
||||||
makerTokenDecimals: number;
|
makerTokenDecimals: number;
|
||||||
takerTokenDecimals: number;
|
takerTokenDecimals: number;
|
||||||
|
@ -49,8 +49,8 @@ const exchangeProxyOverhead = (sourceFlags: number) => {
|
|||||||
|
|
||||||
const buyMarketSideLiquidity: MarketSideLiquidity = {
|
const buyMarketSideLiquidity: MarketSideLiquidity = {
|
||||||
// needed params
|
// needed params
|
||||||
ethToOutputRate: new BigNumber(500),
|
outputAmountPerEth: new BigNumber(500),
|
||||||
ethToInputRate: new BigNumber(1),
|
inputAmountPerEth: new BigNumber(1),
|
||||||
side: MarketOperation.Buy,
|
side: MarketOperation.Buy,
|
||||||
makerTokenDecimals: 18,
|
makerTokenDecimals: 18,
|
||||||
takerTokenDecimals: 18,
|
takerTokenDecimals: 18,
|
||||||
@ -70,8 +70,8 @@ const buyMarketSideLiquidity: MarketSideLiquidity = {
|
|||||||
|
|
||||||
const sellMarketSideLiquidity: MarketSideLiquidity = {
|
const sellMarketSideLiquidity: MarketSideLiquidity = {
|
||||||
// needed params
|
// needed params
|
||||||
ethToOutputRate: new BigNumber(500),
|
outputAmountPerEth: new BigNumber(500),
|
||||||
ethToInputRate: new BigNumber(1),
|
inputAmountPerEth: new BigNumber(1),
|
||||||
side: MarketOperation.Sell,
|
side: MarketOperation.Sell,
|
||||||
makerTokenDecimals: 18,
|
makerTokenDecimals: 18,
|
||||||
takerTokenDecimals: 18,
|
takerTokenDecimals: 18,
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
decodeAffiliateFeeTransformerData,
|
decodeAffiliateFeeTransformerData,
|
||||||
decodeFillQuoteTransformerData,
|
decodeFillQuoteTransformerData,
|
||||||
decodePayTakerTransformerData,
|
decodePayTakerTransformerData,
|
||||||
|
decodePositiveSlippageFeeTransformerData,
|
||||||
decodeWethTransformerData,
|
decodeWethTransformerData,
|
||||||
ETH_TOKEN_ADDRESS,
|
ETH_TOKEN_ADDRESS,
|
||||||
FillQuoteTransformerLimitOrderInfo,
|
FillQuoteTransformerLimitOrderInfo,
|
||||||
@ -17,9 +18,9 @@ import * as chai from 'chai';
|
|||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import 'mocha';
|
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 { 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 {
|
import {
|
||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
OptimizedLimitOrder,
|
OptimizedLimitOrder,
|
||||||
@ -53,6 +54,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
|
|||||||
payTakerTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 2),
|
payTakerTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 2),
|
||||||
fillQuoteTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 3),
|
fillQuoteTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 3),
|
||||||
affiliateFeeTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 4),
|
affiliateFeeTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 4),
|
||||||
|
positiveSlippageFeeTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 5),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let consumer: ExchangeProxySwapQuoteConsumer;
|
let consumer: ExchangeProxySwapQuoteConsumer;
|
||||||
@ -137,11 +139,11 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
|
|||||||
protocolFeeInWeiAmount: getRandomAmount(),
|
protocolFeeInWeiAmount: getRandomAmount(),
|
||||||
feeTakerTokenAmount: getRandomAmount(),
|
feeTakerTokenAmount: getRandomAmount(),
|
||||||
},
|
},
|
||||||
|
makerAmountPerEth: getRandomInteger(1, 1e9),
|
||||||
|
takerAmountPerEth: getRandomInteger(1, 1e9),
|
||||||
...(side === MarketOperation.Buy
|
...(side === MarketOperation.Buy
|
||||||
? { type: MarketOperation.Buy, makerTokenFillAmount }
|
? { type: MarketOperation.Buy, makerTokenFillAmount }
|
||||||
: { type: MarketOperation.Sell, takerTokenFillAmount }),
|
: { type: MarketOperation.Sell, takerTokenFillAmount }),
|
||||||
takerTokenToEthRate: getRandomAmount(),
|
|
||||||
makerTokenToEthRate: getRandomAmount(),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,6 +338,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
|
|||||||
recipient: randomAddress(),
|
recipient: randomAddress(),
|
||||||
buyTokenFeeAmount: getRandomAmount(),
|
buyTokenFeeAmount: getRandomAmount(),
|
||||||
sellTokenFeeAmount: ZERO_AMOUNT,
|
sellTokenFeeAmount: ZERO_AMOUNT,
|
||||||
|
feeType: AffiliateFeeType.PercentageFee,
|
||||||
};
|
};
|
||||||
const callInfo = await consumer.getCalldataOrThrowAsync(quote, {
|
const callInfo = await consumer.getCalldataOrThrowAsync(quote, {
|
||||||
extensionContractOpts: { affiliateFee },
|
extensionContractOpts: { affiliateFee },
|
||||||
@ -349,12 +352,42 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
|
|||||||
{ token: MAKER_TOKEN, amount: affiliateFee.buyTokenFeeAmount, recipient: affiliateFee.recipient },
|
{ 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 () => {
|
it('Throws if a sell token affiliate fee is provided', async () => {
|
||||||
const quote = getRandomSellQuote();
|
const quote = getRandomSellQuote();
|
||||||
const affiliateFee = {
|
const affiliateFee = {
|
||||||
recipient: randomAddress(),
|
recipient: randomAddress(),
|
||||||
buyTokenFeeAmount: ZERO_AMOUNT,
|
buyTokenFeeAmount: ZERO_AMOUNT,
|
||||||
sellTokenFeeAmount: getRandomAmount(),
|
sellTokenFeeAmount: getRandomAmount(),
|
||||||
|
feeType: AffiliateFeeType.PercentageFee,
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
consumer.getCalldataOrThrowAsync(quote, {
|
consumer.getCalldataOrThrowAsync(quote, {
|
||||||
|
@ -756,8 +756,8 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
inputAmount: Web3Wrapper.toBaseUnitAmount(1, 18),
|
inputAmount: Web3Wrapper.toBaseUnitAmount(1, 18),
|
||||||
inputToken: MAKER_TOKEN,
|
inputToken: MAKER_TOKEN,
|
||||||
outputToken: TAKER_TOKEN,
|
outputToken: TAKER_TOKEN,
|
||||||
ethToInputRate: Web3Wrapper.toBaseUnitAmount(1, 18),
|
inputAmountPerEth: Web3Wrapper.toBaseUnitAmount(1, 18),
|
||||||
ethToOutputRate: Web3Wrapper.toBaseUnitAmount(1, 6),
|
outputAmountPerEth: Web3Wrapper.toBaseUnitAmount(1, 6),
|
||||||
quoteSourceFilters: new SourceFilters(),
|
quoteSourceFilters: new SourceFilters(),
|
||||||
makerTokenDecimals: 6,
|
makerTokenDecimals: 6,
|
||||||
takerTokenDecimals: 18,
|
takerTokenDecimals: 18,
|
||||||
@ -1787,7 +1787,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
|
|
||||||
describe('createFills', () => {
|
describe('createFills', () => {
|
||||||
const takerAmount = new BigNumber(5000000);
|
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
|
// tslint:disable-next-line:no-object-literal-type-assertion
|
||||||
const smallOrder: NativeOrderWithFillableAmounts = {
|
const smallOrder: NativeOrderWithFillableAmounts = {
|
||||||
order: {
|
order: {
|
||||||
@ -1830,7 +1830,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
orders,
|
orders,
|
||||||
dexQuotes: [],
|
dexQuotes: [],
|
||||||
targetInput: takerAmount.minus(1),
|
targetInput: takerAmount.minus(1),
|
||||||
ethToOutputRate,
|
outputAmountPerEth,
|
||||||
feeSchedule,
|
feeSchedule,
|
||||||
});
|
});
|
||||||
expect((path[0][0].fillData as NativeFillData).order.maker).to.eq(smallOrder.order.maker);
|
expect((path[0][0].fillData as NativeFillData).order.maker).to.eq(smallOrder.order.maker);
|
||||||
@ -1843,7 +1843,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
orders,
|
orders,
|
||||||
dexQuotes: [],
|
dexQuotes: [],
|
||||||
targetInput: POSITIVE_INF,
|
targetInput: POSITIVE_INF,
|
||||||
ethToOutputRate,
|
outputAmountPerEth,
|
||||||
feeSchedule,
|
feeSchedule,
|
||||||
});
|
});
|
||||||
expect((path[0][0].fillData as NativeFillData).order.maker).to.eq(largeOrder.order.maker);
|
expect((path[0][0].fillData as NativeFillData).order.maker).to.eq(largeOrder.order.maker);
|
||||||
|
@ -39,8 +39,8 @@ export async function getFullyFillableSwapQuoteWithNoFeesAsync(
|
|||||||
worstCaseQuoteInfo: quoteInfo,
|
worstCaseQuoteInfo: quoteInfo,
|
||||||
sourceBreakdown: breakdown,
|
sourceBreakdown: breakdown,
|
||||||
isTwoHop: false,
|
isTwoHop: false,
|
||||||
takerTokenToEthRate: constants.ZERO_AMOUNT,
|
takerAmountPerEth: constants.ZERO_AMOUNT,
|
||||||
makerTokenToEthRate: constants.ZERO_AMOUNT,
|
makerAmountPerEth: constants.ZERO_AMOUNT,
|
||||||
makerTokenDecimals: 18,
|
makerTokenDecimals: 18,
|
||||||
takerTokenDecimals: 18,
|
takerTokenDecimals: 18,
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
{
|
{
|
||||||
"note": "Deploy new FQT",
|
"note": "Deploy new FQT",
|
||||||
"pr": 155
|
"pr": 155
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Deploy new `PositiveSlippageFeeTransformer`",
|
||||||
|
"pr": 101
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -37,7 +37,8 @@
|
|||||||
"wethTransformer": "0xb2bc06a4efb20fc6553a69dbfa49b7be938034a7",
|
"wethTransformer": "0xb2bc06a4efb20fc6553a69dbfa49b7be938034a7",
|
||||||
"payTakerTransformer": "0x4638a7ebe75b911b995d0ec73a81e4f85f41f24e",
|
"payTakerTransformer": "0x4638a7ebe75b911b995d0ec73a81e4f85f41f24e",
|
||||||
"affiliateFeeTransformer": "0xda6d9fc5998f550a094585cf9171f0e8ee3ac59f",
|
"affiliateFeeTransformer": "0xda6d9fc5998f550a094585cf9171f0e8ee3ac59f",
|
||||||
"fillQuoteTransformer": "0x227e767a9b7517681d1cb6b846aa9e541484c7ab"
|
"fillQuoteTransformer": "0x227e767a9b7517681d1cb6b846aa9e541484c7ab",
|
||||||
|
"positiveSlippageFeeTransformer": "0xa9416ce1dbde8d331210c07b5c253d94ee4cc3fd"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"3": {
|
"3": {
|
||||||
@ -78,7 +79,8 @@
|
|||||||
"wethTransformer": "0x05ad19aa3826e0609a19568ffbd1dfe86c6c7184",
|
"wethTransformer": "0x05ad19aa3826e0609a19568ffbd1dfe86c6c7184",
|
||||||
"payTakerTransformer": "0x6d0ebf2bcd9cc93ec553b60ad201943dcca4e291",
|
"payTakerTransformer": "0x6d0ebf2bcd9cc93ec553b60ad201943dcca4e291",
|
||||||
"affiliateFeeTransformer": "0x6588256778ca4432fa43983ac685c45efb2379e2",
|
"affiliateFeeTransformer": "0x6588256778ca4432fa43983ac685c45efb2379e2",
|
||||||
"fillQuoteTransformer": "0x2088a820787ebbe937a0612ef024f1e1d65f9784"
|
"fillQuoteTransformer": "0x2088a820787ebbe937a0612ef024f1e1d65f9784",
|
||||||
|
"positiveSlippageFeeTransformer": "0x8b332f700fd37e71c5c5b26c4d78b5ca63dd33b2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"4": {
|
"4": {
|
||||||
@ -119,7 +121,8 @@
|
|||||||
"wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437",
|
"wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437",
|
||||||
"payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6",
|
"payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6",
|
||||||
"affiliateFeeTransformer": "0xa39b40642e8e00435857a0fe7d0655e08cc2217e",
|
"affiliateFeeTransformer": "0xa39b40642e8e00435857a0fe7d0655e08cc2217e",
|
||||||
"fillQuoteTransformer": "0x3fb85e0c1e9e0ba4ba9a4072442a2540c0473db1"
|
"fillQuoteTransformer": "0x3fb85e0c1e9e0ba4ba9a4072442a2540c0473db1",
|
||||||
|
"positiveSlippageFeeTransformer": "0x0000000000000000000000000000000000000000"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"42": {
|
"42": {
|
||||||
@ -160,7 +163,8 @@
|
|||||||
"wethTransformer": "0x9ce35b5ee9e710535e3988e3f8731d9ca9dba17d",
|
"wethTransformer": "0x9ce35b5ee9e710535e3988e3f8731d9ca9dba17d",
|
||||||
"payTakerTransformer": "0x5a53e7b02a83aa9f60ccf4e424f0442c255bc977",
|
"payTakerTransformer": "0x5a53e7b02a83aa9f60ccf4e424f0442c255bc977",
|
||||||
"affiliateFeeTransformer": "0x870893920a96a28d4b63c0a7d06a521e3bd074b3",
|
"affiliateFeeTransformer": "0x870893920a96a28d4b63c0a7d06a521e3bd074b3",
|
||||||
"fillQuoteTransformer": "0x8d2d732e5fe6d4d6d5e715200b84dfa69fb05478"
|
"fillQuoteTransformer": "0x8d2d732e5fe6d4d6d5e715200b84dfa69fb05478",
|
||||||
|
"positiveSlippageFeeTransformer": "0x0000000000000000000000000000000000000000"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"1337": {
|
"1337": {
|
||||||
@ -201,7 +205,8 @@
|
|||||||
"wethTransformer": "0x7209185959d7227fb77274e1e88151d7c4c368d3",
|
"wethTransformer": "0x7209185959d7227fb77274e1e88151d7c4c368d3",
|
||||||
"payTakerTransformer": "0x3f16ca81691dab9184cb4606c361d73c4fd2510a",
|
"payTakerTransformer": "0x3f16ca81691dab9184cb4606c361d73c4fd2510a",
|
||||||
"affiliateFeeTransformer": "0x99356167edba8fbdc36959e3f5d0c43d1ba9c6db",
|
"affiliateFeeTransformer": "0x99356167edba8fbdc36959e3f5d0c43d1ba9c6db",
|
||||||
"fillQuoteTransformer": "0x45b3a72221e571017c0f0ec42189e11d149d0ace"
|
"fillQuoteTransformer": "0x45b3a72221e571017c0f0ec42189e11d149d0ace",
|
||||||
|
"positiveSlippageFeeTransformer": "0xdd66c23e07b4d6925b6089b5fe6fc9e62941afe8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ export interface ContractAddresses {
|
|||||||
payTakerTransformer: string;
|
payTakerTransformer: string;
|
||||||
fillQuoteTransformer: string;
|
fillQuoteTransformer: string;
|
||||||
affiliateFeeTransformer: string;
|
affiliateFeeTransformer: string;
|
||||||
|
positiveSlippageFeeTransformer: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ import {
|
|||||||
FillQuoteTransformerContract,
|
FillQuoteTransformerContract,
|
||||||
fullMigrateAsync as fullMigrateExchangeProxyAsync,
|
fullMigrateAsync as fullMigrateExchangeProxyAsync,
|
||||||
PayTakerTransformerContract,
|
PayTakerTransformerContract,
|
||||||
|
PositiveSlippageFeeTransformerContract,
|
||||||
WethTransformerContract,
|
WethTransformerContract,
|
||||||
} from '@0x/contracts-zero-ex';
|
} from '@0x/contracts-zero-ex';
|
||||||
import { Web3ProviderEngine } from '@0x/subproviders';
|
import { Web3ProviderEngine } from '@0x/subproviders';
|
||||||
@ -345,7 +346,12 @@ export async function runMigrationsAsync(
|
|||||||
bridgeAdapter.address,
|
bridgeAdapter.address,
|
||||||
exchangeProxy.address,
|
exchangeProxy.address,
|
||||||
);
|
);
|
||||||
|
const positiveSlippageFeeTransformer = await PositiveSlippageFeeTransformerContract.deployFrom0xArtifactAsync(
|
||||||
|
exchangeProxyArtifacts.PositiveSlippageFeeTransformer,
|
||||||
|
provider,
|
||||||
|
txDefaults,
|
||||||
|
allArtifacts,
|
||||||
|
);
|
||||||
const contractAddresses = {
|
const contractAddresses = {
|
||||||
erc20Proxy: erc20Proxy.address,
|
erc20Proxy: erc20Proxy.address,
|
||||||
erc721Proxy: erc721Proxy.address,
|
erc721Proxy: erc721Proxy.address,
|
||||||
@ -385,6 +391,7 @@ export async function runMigrationsAsync(
|
|||||||
payTakerTransformer: payTakerTransformer.address,
|
payTakerTransformer: payTakerTransformer.address,
|
||||||
fillQuoteTransformer: fillQuoteTransformer.address,
|
fillQuoteTransformer: fillQuoteTransformer.address,
|
||||||
affiliateFeeTransformer: affiliateFeeTransformer.address,
|
affiliateFeeTransformer: affiliateFeeTransformer.address,
|
||||||
|
positiveSlippageFeeTransformer: positiveSlippageFeeTransformer.address,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return contractAddresses;
|
return contractAddresses;
|
||||||
|
@ -77,6 +77,9 @@ export {
|
|||||||
AffiliateFeeTransformerData,
|
AffiliateFeeTransformerData,
|
||||||
encodeAffiliateFeeTransformerData,
|
encodeAffiliateFeeTransformerData,
|
||||||
decodeAffiliateFeeTransformerData,
|
decodeAffiliateFeeTransformerData,
|
||||||
|
PositiveSlippageFeeTransformerData,
|
||||||
|
encodePositiveSlippageFeeTransformerData,
|
||||||
|
decodePositiveSlippageFeeTransformerData,
|
||||||
findTransformerNonce,
|
findTransformerNonce,
|
||||||
getTransformerAddress,
|
getTransformerAddress,
|
||||||
} from './transformer_utils';
|
} 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({
|
export const affiliateFeeTransformerDataEncoder = AbiEncoder.create({
|
||||||
name: 'data',
|
name: 'data',
|
||||||
@ -195,6 +195,42 @@ export function decodeAffiliateFeeTransformerData(encoded: string): AffiliateFee
|
|||||||
return affiliateFeeTransformerDataEncoder.decode(encoded);
|
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.
|
* Find the nonce for a transformer given its deployer.
|
||||||
* If `deployer` is the null address, zero will always be returned.
|
* 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({
|
export const affiliateFeeTransformerDataEncoder = AbiEncoder.create({
|
||||||
name: 'data',
|
name: 'data',
|
||||||
@ -317,3 +317,39 @@ export function getTransformerAddress(deployer: string, nonce: number): string {
|
|||||||
ethjs.rlphash([deployer, nonce] as any).slice(12),
|
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