diff --git a/contracts/zero-ex/CHANGELOG.json b/contracts/zero-ex/CHANGELOG.json index 0677d58c48..3229390560 100644 --- a/contracts/zero-ex/CHANGELOG.json +++ b/contracts/zero-ex/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "0.36.0", + "changes": [ + { + "note": "Add Synthetix support in Ethereum and Optimism bridge adapters", + "pr": 518 + } + ] + }, { "version": "0.35.0", "changes": [ diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol index bc9078ed62..feaa19f248 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol @@ -57,4 +57,5 @@ library BridgeProtocols { uint128 internal constant PLATYPUS = 27; uint128 internal constant BANCORV3 = 28; uint128 internal constant VELODROME = 29; + uint128 internal constant SYNTHETIX = 30; } diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/EthereumBridgeAdapter.sol b/contracts/zero-ex/contracts/src/transformers/bridges/EthereumBridgeAdapter.sol index d238836ff3..491430b2d1 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/EthereumBridgeAdapter.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/EthereumBridgeAdapter.sol @@ -41,6 +41,7 @@ import "./mixins/MixinMooniswap.sol"; import "./mixins/MixinMStable.sol"; import "./mixins/MixinNerve.sol"; import "./mixins/MixinShell.sol"; +import "./mixins/MixinSynthetix.sol"; import "./mixins/MixinUniswap.sol"; import "./mixins/MixinUniswapV2.sol"; import "./mixins/MixinUniswapV3.sol"; @@ -67,6 +68,7 @@ contract EthereumBridgeAdapter is MixinMStable, MixinNerve, MixinShell, + MixinSynthetix, MixinUniswap, MixinUniswapV2, MixinUniswapV3, @@ -260,6 +262,12 @@ contract EthereumBridgeAdapter is sellAmount, order.bridgeData ); + } else if (protocolId == BridgeProtocols.SYNTHETIX) { + if (dryRun) { return (0, true); } + boughtAmount = _tradeSynthetix( + sellAmount, + order.bridgeData + ); } else if (protocolId == BridgeProtocols.UNKNOWN) { if (dryRun) { return (0, true); } boughtAmount = _tradeZeroExBridge( diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/OptimismBridgeAdapter.sol b/contracts/zero-ex/contracts/src/transformers/bridges/OptimismBridgeAdapter.sol index 6a1bb03943..de3565c10a 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/OptimismBridgeAdapter.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/OptimismBridgeAdapter.sol @@ -25,6 +25,7 @@ import "./BridgeProtocols.sol"; import "./mixins/MixinCurve.sol"; import "./mixins/MixinCurveV2.sol"; import "./mixins/MixinNerve.sol"; +import "./mixins/MixinSynthetix.sol"; import "./mixins/MixinUniswapV3.sol"; import "./mixins/MixinVelodrome.sol"; import "./mixins/MixinZeroExBridge.sol"; @@ -34,6 +35,7 @@ contract OptimismBridgeAdapter is MixinCurve, MixinCurveV2, MixinNerve, + MixinSynthetix, MixinUniswapV3, MixinVelodrome, MixinZeroExBridge @@ -93,6 +95,12 @@ contract OptimismBridgeAdapter is sellAmount, order.bridgeData ); + } else if (protocolId == BridgeProtocols.SYNTHETIX) { + if (dryRun) { return (0, true); } + boughtAmount = _tradeSynthetix( + sellAmount, + order.bridgeData + ); } else if (protocolId == BridgeProtocols.UNKNOWN) { if (dryRun) { return (0, true); } boughtAmount = _tradeZeroExBridge( diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinSynthetix.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinSynthetix.sol new file mode 100644 index 0000000000..2e10ffb27c --- /dev/null +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinSynthetix.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + + Copyright 2022 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; + +interface ISynthetix { + // Ethereum Mainnet + function exchangeAtomically( + bytes32 sourceCurrencyKey, + uint256 sourceAmount, + bytes32 destinationCurrencyKey, + bytes32 trackingCode, + uint256 minAmount + ) external returns (uint256 amountReceived); + + // Optimism + function exchangeWithTracking( + bytes32 sourceCurrencyKey, + uint256 sourceAmount, + bytes32 destinationCurrencyKey, + address rewardAddress, + bytes32 trackingCode + ) external returns (uint256 amountReceived); +} + +contract MixinSynthetix { + address private constant rewardAddress = + 0x5C80239D97E1eB216b5c3D8fBa5DE5Be5d38e4C9; + bytes32 constant trackingCode = + 0x3058000000000000000000000000000000000000000000000000000000000000; + + function _tradeSynthetix(uint256 sellAmount, bytes memory bridgeData) + public + returns (uint256 boughtAmount) + { + ( + ISynthetix synthetix, + bytes32 sourceCurrencyKey, + bytes32 destinationCurrencyKey + ) = abi.decode( + bridgeData, + (ISynthetix, bytes32, bytes32) + ); + + boughtAmount = exchange( + synthetix, + sourceCurrencyKey, + destinationCurrencyKey, + sellAmount + ); + } + + function exchange( + ISynthetix synthetix, + bytes32 sourceCurrencyKey, + bytes32 destinationCurrencyKey, + uint256 sellAmount + ) internal returns (uint256 boughtAmount) { + uint256 chainId; + assembly { + chainId := chainid() + } + + if (chainId == 1) { + boughtAmount = synthetix.exchangeAtomically( + sourceCurrencyKey, + sellAmount, + destinationCurrencyKey, + trackingCode, + 0 + ); + } else { + boughtAmount = synthetix.exchangeWithTracking( + sourceCurrencyKey, + sellAmount, + destinationCurrencyKey, + rewardAddress, + trackingCode + ); + } + } +} diff --git a/contracts/zero-ex/package.json b/contracts/zero-ex/package.json index c9859fa9f9..8c4e17955e 100644 --- a/contracts/zero-ex/package.json +++ b/contracts/zero-ex/package.json @@ -43,7 +43,7 @@ "config": { "publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature,AvalancheBridgeAdapter,BSCBridgeAdapter,CeloBridgeAdapter,EthereumBridgeAdapter,FantomBridgeAdapter,OptimismBridgeAdapter,PolygonBridgeAdapter", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(AbstractBridgeAdapter|AffiliateFeeTransformer|AvalancheBridgeAdapter|BSCBridgeAdapter|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeProtocols|CeloBridgeAdapter|CurveLiquidityProvider|ERC1155OrdersFeature|ERC165Feature|ERC721OrdersFeature|EthereumBridgeAdapter|FantomBridgeAdapter|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinERC1155Spender|FixinERC721Spender|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC1155OrdersFeature|IERC1155Token|IERC165Feature|IERC20Bridge|IERC20Transformer|IERC721OrdersFeature|IERC721Token|IFeature|IFeeRecipient|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|IPropertyValidator|ISimpleFunctionRegistryFeature|IStaking|ITakerCallback|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC1155OrdersStorage|LibERC20Transformer|LibERC721OrdersStorage|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNFTOrder|LibNFTOrdersRichErrors|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAaveV2|MixinBalancer|MixinBalancerV2|MixinBalancerV2Batch|MixinBancor|MixinBancorV3|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinGMX|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinPlatypus|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinVelodrome|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NFTOrders|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OptimismBridgeAdapter|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PolygonBridgeAdapter|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFeeRecipient|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC1155Token|TestMintableERC20Token|TestMintableERC721Token|TestMooniswap|TestNFTOrderPresigner|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestPropertyValidator|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json" + "abis": "./test/generated-artifacts/@(AbstractBridgeAdapter|AffiliateFeeTransformer|AvalancheBridgeAdapter|BSCBridgeAdapter|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeProtocols|CeloBridgeAdapter|CurveLiquidityProvider|ERC1155OrdersFeature|ERC165Feature|ERC721OrdersFeature|EthereumBridgeAdapter|FantomBridgeAdapter|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinERC1155Spender|FixinERC721Spender|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC1155OrdersFeature|IERC1155Token|IERC165Feature|IERC20Bridge|IERC20Transformer|IERC721OrdersFeature|IERC721Token|IFeature|IFeeRecipient|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|IPropertyValidator|ISimpleFunctionRegistryFeature|IStaking|ITakerCallback|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC1155OrdersStorage|LibERC20Transformer|LibERC721OrdersStorage|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNFTOrder|LibNFTOrdersRichErrors|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAaveV2|MixinBalancer|MixinBalancerV2|MixinBalancerV2Batch|MixinBancor|MixinBancorV3|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinGMX|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinPlatypus|MixinShell|MixinSynthetix|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinVelodrome|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NFTOrders|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OptimismBridgeAdapter|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PolygonBridgeAdapter|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFeeRecipient|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC1155Token|TestMintableERC20Token|TestMintableERC721Token|TestMooniswap|TestNFTOrderPresigner|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestPropertyValidator|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json" }, "repository": { "type": "git", diff --git a/contracts/zero-ex/test/artifacts.ts b/contracts/zero-ex/test/artifacts.ts index e82ed6b15e..82b7284332 100644 --- a/contracts/zero-ex/test/artifacts.ts +++ b/contracts/zero-ex/test/artifacts.ts @@ -124,6 +124,7 @@ import * as MixinMStable from '../test/generated-artifacts/MixinMStable.json'; import * as MixinNerve from '../test/generated-artifacts/MixinNerve.json'; import * as MixinPlatypus from '../test/generated-artifacts/MixinPlatypus.json'; import * as MixinShell from '../test/generated-artifacts/MixinShell.json'; +import * as MixinSynthetix from '../test/generated-artifacts/MixinSynthetix.json'; import * as MixinUniswap from '../test/generated-artifacts/MixinUniswap.json'; import * as MixinUniswapV2 from '../test/generated-artifacts/MixinUniswapV2.json'; import * as MixinUniswapV3 from '../test/generated-artifacts/MixinUniswapV3.json'; @@ -347,6 +348,7 @@ export const artifacts = { MixinNerve: MixinNerve as ContractArtifact, MixinPlatypus: MixinPlatypus as ContractArtifact, MixinShell: MixinShell as ContractArtifact, + MixinSynthetix: MixinSynthetix as ContractArtifact, MixinUniswap: MixinUniswap as ContractArtifact, MixinUniswapV2: MixinUniswapV2 as ContractArtifact, MixinUniswapV3: MixinUniswapV3 as ContractArtifact, diff --git a/contracts/zero-ex/test/wrappers.ts b/contracts/zero-ex/test/wrappers.ts index 7391c14dd0..ca034da2a4 100644 --- a/contracts/zero-ex/test/wrappers.ts +++ b/contracts/zero-ex/test/wrappers.ts @@ -122,6 +122,7 @@ export * from '../test/generated-wrappers/mixin_mooniswap'; export * from '../test/generated-wrappers/mixin_nerve'; export * from '../test/generated-wrappers/mixin_platypus'; export * from '../test/generated-wrappers/mixin_shell'; +export * from '../test/generated-wrappers/mixin_synthetix'; export * from '../test/generated-wrappers/mixin_uniswap'; export * from '../test/generated-wrappers/mixin_uniswap_v2'; export * from '../test/generated-wrappers/mixin_uniswap_v3'; diff --git a/contracts/zero-ex/tsconfig.json b/contracts/zero-ex/tsconfig.json index e4916c2c2b..cb6dfa4f9e 100644 --- a/contracts/zero-ex/tsconfig.json +++ b/contracts/zero-ex/tsconfig.json @@ -161,6 +161,7 @@ "test/generated-artifacts/MixinNerve.json", "test/generated-artifacts/MixinPlatypus.json", "test/generated-artifacts/MixinShell.json", + "test/generated-artifacts/MixinSynthetix.json", "test/generated-artifacts/MixinUniswap.json", "test/generated-artifacts/MixinUniswapV2.json", "test/generated-artifacts/MixinUniswapV3.json", diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 93f1af57d2..37c912841f 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "Refactor `TokenAdjacency` and `TokenAdjacencyBuilder`", "pr": 517 + }, + { + "note": "Add Synthetix support`", + "pr": 518 } ] }, diff --git a/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol b/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol index 47986ad9aa..cdb20615f2 100644 --- a/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol +++ b/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol @@ -39,6 +39,7 @@ import "./MooniswapSampler.sol"; import "./NativeOrderSampler.sol"; import "./PlatypusSampler.sol"; import "./ShellSampler.sol"; +import "./SynthetixSampler.sol"; import "./TwoHopSampler.sol"; import "./UniswapSampler.sol"; import "./UniswapV2Sampler.sol"; @@ -67,6 +68,7 @@ contract ERC20BridgeSampler is NativeOrderSampler, PlatypusSampler, ShellSampler, + SynthetixSampler, TwoHopSampler, UniswapSampler, UniswapV2Sampler, diff --git a/packages/asset-swapper/contracts/src/SynthetixSampler.sol b/packages/asset-swapper/contracts/src/SynthetixSampler.sol new file mode 100644 index 0000000000..38e45ebc6b --- /dev/null +++ b/packages/asset-swapper/contracts/src/SynthetixSampler.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + + Copyright 2022 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; +pragma experimental ABIEncoderV2; + +interface IReadProxyAddressResolver { + function target() external view returns (address); +} + +interface IAddressResolver { + function getAddress(bytes32 name) external view returns (address); +} + +interface IExchanger { + // Ethereum Mainnet + function getAmountsForAtomicExchange( + uint256 sourceAmount, + bytes32 sourceCurrencyKey, + bytes32 destinationCurrencyKey + ) + external + view + returns ( + uint256 amountReceived, + uint256 fee, + uint256 exchangeFeeRate + ); + + // Optimism + function getAmountsForExchange( + uint256 sourceAmount, + bytes32 sourceCurrencyKey, + bytes32 destinationCurrencyKey + ) + external + view + returns ( + uint256 amountReceived, + uint256 fee, + uint256 exchangeFeeRate + ); +} + +contract SynthetixSampler { + + /// @dev Sample sell quotes from Synthetix Atomic Swap. + /// @param takerTokenSymbol Symbol (currency key) of the taker token (what to sell). + /// @param makerTokenSymbol Symbol (currency key) of the maker token (what to buy). + /// @param takerTokenAmounts Taker token sell amount for each sample (sorted in ascending order). + /// @return synthetix Synthetix address. + /// @return makerTokenAmounts Maker amounts bought at each taker token amount. + function sampleSellsFromSynthetix( + IReadProxyAddressResolver readProxy, + bytes32 takerTokenSymbol, + bytes32 makerTokenSymbol, + uint256[] memory takerTokenAmounts + ) public view returns (address synthetix, uint256[] memory makerTokenAmounts) { + synthetix = getSynthetixAddress(readProxy); + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + if (numSamples == 0) { + return (synthetix, makerTokenAmounts); + } + + makerTokenAmounts[0] = exchange( + readProxy, + takerTokenAmounts[0], + takerTokenSymbol, + makerTokenSymbol + ); + + // Synthetix atomic swap has a fixed rate. Calculate the rest based on the first value (and save gas). + for (uint256 i = 1; i < numSamples; i++) { + makerTokenAmounts[i] = + (makerTokenAmounts[0] * takerTokenAmounts[i]) / + takerTokenAmounts[0]; + } + } + + /// @dev Sample buy quotes from Synthetix Atomic Swap. + /// @param takerTokenSymbol Symbol (currency key) of the taker token (what to sell). + /// @param makerTokenSymbol Symbol (currency key) of the maker token (what to buy). + /// @param makerTokenAmounts Maker token buy amount for each sample (sorted in ascending order). + /// @return synthetix Synthetix address. + /// @return takerTokenAmounts Taker amounts sold at each maker token amount. + function sampleBuysFromSynthetix( + IReadProxyAddressResolver readProxy, + bytes32 takerTokenSymbol, + bytes32 makerTokenSymbol, + uint256[] memory makerTokenAmounts + ) public view returns (address synthetix, uint256[] memory takerTokenAmounts) { + synthetix = getSynthetixAddress(readProxy); + // Since Synthetix atomic have a fixed rate, we can pick any reasonablely size takerTokenAmount (fixed to 1 ether here) and calculate the rest. + uint256 amountReceivedForEther = exchange( + readProxy, + 1 ether, + takerTokenSymbol, + makerTokenSymbol + ); + + uint256 numSamples = makerTokenAmounts.length; + takerTokenAmounts = new uint256[](numSamples); + + for (uint256 i = 0; i < numSamples; i++) { + takerTokenAmounts[i] = + (1 ether * makerTokenAmounts[i]) / + amountReceivedForEther; + } + } + + function exchange( + IReadProxyAddressResolver readProxy, + uint256 sourceAmount, + bytes32 sourceCurrencyKey, + bytes32 destinationCurrencyKey + ) private view returns (uint256 amountReceived) { + IExchanger exchanger = getExchanger(readProxy); + uint256 chainId; + assembly { + chainId := chainid() + } + + if (chainId == 1) { + (amountReceived, , ) = exchanger.getAmountsForAtomicExchange( + sourceAmount, + sourceCurrencyKey, + destinationCurrencyKey + ); + } else { + (amountReceived, , ) = exchanger.getAmountsForExchange( + sourceAmount, + sourceCurrencyKey, + destinationCurrencyKey + ); + } + } + + function getSynthetixAddress(IReadProxyAddressResolver readProxy) + private + view + returns (address) + { + return IAddressResolver(readProxy.target()).getAddress("Synthetix"); + } + + function getExchanger(IReadProxyAddressResolver readProxy) + private + view + returns (IExchanger) + { + return + IExchanger( + IAddressResolver(readProxy.target()).getAddress("Exchanger") + ); + } +} diff --git a/packages/asset-swapper/package.json b/packages/asset-swapper/package.json index 6de2b36960..0b79d996f4 100644 --- a/packages/asset-swapper/package.json +++ b/packages/asset-swapper/package.json @@ -40,7 +40,7 @@ "config": { "publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2BatchSampler|BalancerV2Common|BalancerV2Sampler|BancorSampler|BancorV3Sampler|CompoundSampler|CurveSampler|DODOSampler|DODOV2Sampler|ERC20BridgeSampler|FakeTaker|GMXSampler|IBalancer|IBalancerV2Vault|IBancor|IBancorV3|ICurve|IGMX|IMStable|IMooniswap|IMultiBridge|IPlatypus|IShell|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|NativeOrderSampler|PlatypusSampler|SamplerUtils|ShellSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler|VelodromeSampler).json", + "abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2BatchSampler|BalancerV2Common|BalancerV2Sampler|BancorSampler|BancorV3Sampler|CompoundSampler|CurveSampler|DODOSampler|DODOV2Sampler|ERC20BridgeSampler|FakeTaker|GMXSampler|IBalancer|IBalancerV2Vault|IBancor|IBancorV3|ICurve|IGMX|IMStable|IMooniswap|IMultiBridge|IPlatypus|IShell|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|NativeOrderSampler|PlatypusSampler|SamplerUtils|ShellSampler|SynthetixSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler|VelodromeSampler).json", "postpublish": { "assets": [] } diff --git a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts index 2b35549698..e33995308b 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts @@ -1,7 +1,7 @@ import { ChainId, getContractAddressesForChainOrThrow } from '@0x/contract-addresses'; import { FillQuoteTransformerOrderType } from '@0x/protocol-utils'; import { BigNumber } from '@0x/utils'; -import { formatBytes32String } from '@ethersproject/strings'; +import { formatBytes32String, parseBytes32String } from '@ethersproject/strings'; import { TokenAdjacencyGraph, TokenAdjacencyGraphBuilder } from '../token_adjacency_graph'; @@ -32,6 +32,7 @@ import { MultiHopFillData, PlatypusInfo, PsmInfo, + SynthetixFillData, UniswapV2FillData, UniswapV3FillData, } from './types'; @@ -107,6 +108,7 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId( ERC20BridgeSource.CurveV2, ERC20BridgeSource.ShibaSwap, ERC20BridgeSource.Synapse, + ERC20BridgeSource.Synthetix, // TODO: enable after FQT has been redeployed on Ethereum mainnet // ERC20BridgeSource.AaveV2, // ERC20BridgeSource.Compound, @@ -216,6 +218,7 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId( ERC20BridgeSource.CurveV2, ERC20BridgeSource.MultiHop, ERC20BridgeSource.Velodrome, + ERC20BridgeSource.Synthetix, ]), }, new SourceFilters([]), @@ -255,6 +258,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId( ERC20BridgeSource.CurveV2, ERC20BridgeSource.ShibaSwap, ERC20BridgeSource.Synapse, + ERC20BridgeSource.Synthetix, // TODO: enable after FQT has been redeployed on Ethereum mainnet // ERC20BridgeSource.AaveV2, // ERC20BridgeSource.Compound, @@ -364,6 +368,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId( ERC20BridgeSource.CurveV2, ERC20BridgeSource.MultiHop, ERC20BridgeSource.Velodrome, + ERC20BridgeSource.Synthetix, ]), }, new SourceFilters([]), @@ -460,6 +465,11 @@ export const MAINNET_TOKENS = { EURS: '0xdb25f211ab05b1c97d595516f45794528a807ad8', sEUR: '0xd71ecff9342a5ced620049e616c5035f1db98620', sETH: '0x5e74c9036fb86bd7ecdcb084a0673efc32ea31cb', + sJPY: '0xf6b1c627e95bfc3c1b4c9b825a032ff0fbf3e07d', + sGBP: '0x97fe22e7341a0cd8db6f6c021a24dc8f4dad855f', + sAUD: '0xf48e200eaf9906362bb1442fca31e0835773b8b4', + sKRW: '0x269895a3df4d73b077fc823dd6da1b95f72aaf9b', + sCHF: '0x0f83287ff768d1c1e17a42f44d644d7f22e8ee1d', stETH: '0xae7ab96520de3a18e5e111b5eaab095312d7fe84', wstETH: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', LINK: '0x514910771af9ca656af840dff83e8264ecf986ca', @@ -642,10 +652,6 @@ export const FANTOM_TOKENS = { gMIM: '0xc664fc7b8487a3e10824cda768c1d239f2403bbe', }; -export const GEIST_FANTOM_POOLS = { - lendingPool: '0x9fad24f572045c7869117160a571b2e50b10d068', -}; - export const OPTIMISM_TOKENS = { WETH: '0x4200000000000000000000000000000000000006', USDC: '0x7f5c764cbc14f9669b88837ca1490cca17c31607', @@ -654,6 +660,21 @@ export const OPTIMISM_TOKENS = { WBTC: '0x68f180fcce6836688e9084f035309e29bf0a2095', nETH: '0x809dc529f07651bd43a172e8db6f4a7a0d771036', sWETH: '0x121ab82b49b2bc4c7901ca46b8277962b4350204', + // Synthetix synths: + sAAVE: '0x00b8d5a5e1ac97cb4341c4bc4367443c8776e8d9', + sAVAX: '0xb2b42b231c68cbb0b4bf2ffebf57782fd97d3da4', + sBTC: '0x298b9b95708152ff6968aafd889c6586e9169f1d', + sETH: '0xe405de8f52ba7559f9df3c368500b6e6ae6cee49', + sEUR: '0xfbc4198702e81ae77c06d58f81b629bdf36f0a71', + sLINK: '0xc5db22719a06418028a40a9b5e9a7c02959d0d08', + sMATIC: '0x81ddfac111913d3d5218dea999216323b7cd6356', + sSOL: '0x8b2f7ae8ca8ee8428b6d76de88326bb413db2766', + sUNI: '0xf5a6115aa582fd1beea22bc93b7dc7a785f60d03', + sUSD: '0x8c6f28f2f1a3c87f0f938b96d27520d9751ec8d9', +}; + +export const GEIST_FANTOM_POOLS = { + lendingPool: '0x9fad24f572045c7869117160a571b2e50b10d068', }; export const CURVE_POOLS = { @@ -947,8 +968,19 @@ export const DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID = valueByChainId { + // Synthetix Atomic Swap + builder.addCompleteSubgraph([ + OPTIMISM_TOKENS.sAAVE, + OPTIMISM_TOKENS.sAVAX, + OPTIMISM_TOKENS.sBTC, + OPTIMISM_TOKENS.sETH, + OPTIMISM_TOKENS.sEUR, + OPTIMISM_TOKENS.sLINK, + OPTIMISM_TOKENS.sMATIC, + OPTIMISM_TOKENS.sSOL, + OPTIMISM_TOKENS.sUNI, + OPTIMISM_TOKENS.sUSD, + ]); + }) + .build(), }, TokenAdjacencyGraph.getEmptyGraph(), ); @@ -2365,6 +2411,47 @@ export const VELODROME_ROUTER_BY_CHAIN_ID = valueByChainId( NULL_ADDRESS, ); +export const SYNTHETIX_READ_PROXY_BY_CHAIN_ID = valueByChainId( + { + [ChainId.Mainnet]: '0x4e3b31eb0e5cb73641ee1e65e7dcefe520ba3ef2', + [ChainId.Optimism]: '0x1cb059b7e74fd21665968c908806143e744d5f30', + }, + NULL_ADDRESS, +); + +export const SYNTHETIX_CURRENCY_KEYS_BY_CHAIN_ID = valueByChainId>( + { + // There is no easy way to find out what synths are supported on mainnet. + // The below list is based on https://sips.synthetix.io/sccp/sccp-190. + [ChainId.Mainnet]: new Map([ + [MAINNET_TOKENS.sAUD, 'sAUD'], + [MAINNET_TOKENS.sBTC, 'sBTC'], + [MAINNET_TOKENS.sCHF, 'sCHF'], + [MAINNET_TOKENS.sETH, 'sETH'], + [MAINNET_TOKENS.sEUR, 'sEUR'], + [MAINNET_TOKENS.sGBP, 'sGBP'], + [MAINNET_TOKENS.sJPY, 'sJPY'], + [MAINNET_TOKENS.sKRW, 'sKRW'], + [MAINNET_TOKENS.sUSD, 'sUSD'], + ]), + // Supported assets can be find through SynthUtil::synthsRates. + // Low liquidity tokens can be excluded. + [ChainId.Optimism]: new Map([ + [OPTIMISM_TOKENS.sAAVE, 'sAAVE'], + [OPTIMISM_TOKENS.sAVAX, 'sAVAX'], + [OPTIMISM_TOKENS.sBTC, 'sBTC'], + [OPTIMISM_TOKENS.sETH, 'sETH'], + [OPTIMISM_TOKENS.sEUR, 'sEUR'], + [OPTIMISM_TOKENS.sLINK, 'sLINK'], + [OPTIMISM_TOKENS.sMATIC, 'sMATIC'], + [OPTIMISM_TOKENS.sSOL, 'sSOL'], + [OPTIMISM_TOKENS.sUNI, 'sUNI'], + [OPTIMISM_TOKENS.sUSD, 'sUSD'], + ]), + }, + new Map(), +); + export const VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID = valueByChainId( { [ChainId.Mainnet]: [ @@ -2548,7 +2635,28 @@ export const DEFAULT_GAS_SCHEDULE: Required = { return compoundFillData.takerToken === wethAddress ? 210e3 : 250e3; } }, + [ERC20BridgeSource.Synthetix]: (fillData?: FillData) => { + const { chainId, makerTokenSymbolBytes32, takerTokenSymbolBytes32 } = fillData as SynthetixFillData; + const makerTokenSymbol = parseBytes32String(makerTokenSymbolBytes32); + const takerTokenSymbol = parseBytes32String(takerTokenSymbolBytes32); + // Gas cost widely varies by token on mainnet. + if (chainId === ChainId.Mainnet) { + if (takerTokenSymbol === 'sBTC' || makerTokenSymbol === 'sBTC') { + return 800e3; + } + if (takerTokenSymbol === 'sETH' || makerTokenSymbol === 'sETH') { + return 700e3; + } + return 580e3; + } + + // Optimism + if (takerTokenSymbol === 'sUSD' || makerTokenSymbol === 'sUSD') { + return 480e3; + } + return 580e3; + }, // // BSC // diff --git a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts index 23867c23bf..73b3ff0ba7 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts @@ -38,6 +38,7 @@ import { OrderDomain, PlatypusFillData, ShellFillData, + SynthetixFillData, UniswapV2FillData, UniswapV3FillData, UniswapV3PathAmount, @@ -210,6 +211,8 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s return encodeBridgeSourceId(BridgeProtocol.BancorV3, 'BancorV3'); case ERC20BridgeSource.Velodrome: return encodeBridgeSourceId(BridgeProtocol.Velodrome, 'Velodrome'); + case ERC20BridgeSource.Synthetix: + return encodeBridgeSourceId(BridgeProtocol.Synthetix, 'Synthetix'); default: throw new Error(AggregationError.NoBridgeForSource); } @@ -391,6 +394,14 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder const velodromeFillData = (order as OptimizedMarketBridgeOrder).fillData; bridgeData = encoder.encode([velodromeFillData.router, velodromeFillData.stable]); break; + case ERC20BridgeSource.Synthetix: + const fillData = (order as OptimizedMarketBridgeOrder).fillData; + bridgeData = encoder.encode([ + fillData.synthetix, + fillData.takerTokenSymbolBytes32, + fillData.makerTokenSymbolBytes32, + ]); + break; default: throw new Error(AggregationError.NoBridgeForSource); } @@ -517,6 +528,7 @@ export const BRIDGE_ENCODERS: { [ERC20BridgeSource.Compound]: AbiEncoder.create('(address)'), [ERC20BridgeSource.Geist]: AbiEncoder.create('(address,address)'), [ERC20BridgeSource.Velodrome]: AbiEncoder.create('(address,bool)'), + [ERC20BridgeSource.Synthetix]: AbiEncoder.create('(address,bytes32,bytes32)'), }; function getFillTokenAmounts(fill: Fill, side: MarketOperation): [BigNumber, BigNumber] { diff --git a/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts b/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts index 9ec4fea9e9..7ed26814ee 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts @@ -1,6 +1,7 @@ import { ChainId } from '@0x/contract-addresses'; import { LimitOrderFields } from '@0x/protocol-utils'; import { BigNumber, logUtils } from '@0x/utils'; +import { formatBytes32String } from '@ethersproject/strings'; import * as _ from 'lodash'; import { AaveV2Sampler } from '../../noop_samplers/AaveV2Sampler'; @@ -47,6 +48,8 @@ import { NULL_ADDRESS, PLATYPUS_ROUTER_BY_CHAIN_ID, SELL_SOURCE_FILTER_BY_CHAIN_ID, + SYNTHETIX_CURRENCY_KEYS_BY_CHAIN_ID, + SYNTHETIX_READ_PROXY_BY_CHAIN_ID, UNISWAPV1_ROUTER_BY_CHAIN_ID, UNISWAPV3_CONFIG_BY_CHAIN_ID, VELODROME_ROUTER_BY_CHAIN_ID, @@ -94,6 +97,7 @@ import { ShellFillData, SourceQuoteOperation, SourcesWithPoolsCache, + SynthetixFillData, UniswapV2FillData, UniswapV3FillData, VelodromeFillData, @@ -1309,6 +1313,60 @@ export class SamplerOperations { }); } + public getSynthetixSellQuotes( + readProxy: string, + takerTokenSymbol: string, + makerTokenSymbol: string, + takerFillAmounts: BigNumber[], + ): SourceQuoteOperation { + const takerTokenSymbolBytes32 = formatBytes32String(takerTokenSymbol); + const makerTokenSymbolBytes32 = formatBytes32String(makerTokenSymbol); + return new SamplerContractOperation({ + source: ERC20BridgeSource.Synthetix, + contract: this._samplerContract, + function: this._samplerContract.sampleSellsFromSynthetix, + params: [readProxy, takerTokenSymbolBytes32, makerTokenSymbolBytes32, takerFillAmounts], + callback: (callResults: string, fillData: SynthetixFillData): BigNumber[] => { + const [synthetix, samples] = this._samplerContract.getABIDecodedReturnData<[string, BigNumber[]]>( + 'sampleSellsFromSynthetix', + callResults, + ); + fillData.synthetix = synthetix; + fillData.takerTokenSymbolBytes32 = takerTokenSymbolBytes32; + fillData.makerTokenSymbolBytes32 = makerTokenSymbolBytes32; + fillData.chainId = this.chainId; + return samples; + }, + }); + } + + public getSynthetixBuyQuotes( + readProxy: string, + takerTokenSymbol: string, + makerTokenSymbol: string, + makerFillAmounts: BigNumber[], + ): SourceQuoteOperation { + const takerTokenSymbolBytes32 = formatBytes32String(takerTokenSymbol); + const makerTokenSymbolBytes32 = formatBytes32String(makerTokenSymbol); + return new SamplerContractOperation({ + source: ERC20BridgeSource.Synthetix, + contract: this._samplerContract, + function: this._samplerContract.sampleBuysFromSynthetix, + params: [readProxy, takerTokenSymbolBytes32, makerTokenSymbolBytes32, makerFillAmounts], + callback: (callResults: string, fillData: SynthetixFillData): BigNumber[] => { + const [synthetix, samples] = this._samplerContract.getABIDecodedReturnData<[string, BigNumber[]]>( + 'sampleBuysFromSynthetix', + callResults, + ); + fillData.synthetix = synthetix; + fillData.takerTokenSymbolBytes32 = takerTokenSymbolBytes32; + fillData.makerTokenSymbolBytes32 = makerTokenSymbolBytes32; + fillData.chainId = this.chainId; + return samples; + }, + }); + } + /** * Returns the best price for the native token * Best is calculated according to the fee schedule, so the price of the @@ -1736,6 +1794,21 @@ export class SamplerOperations { takerFillAmounts, ); } + case ERC20BridgeSource.Synthetix: { + const readProxy = SYNTHETIX_READ_PROXY_BY_CHAIN_ID[this.chainId]; + const currencyKeyMap = SYNTHETIX_CURRENCY_KEYS_BY_CHAIN_ID[this.chainId]; + const takerTokenSymbol = currencyKeyMap.get(takerToken.toLowerCase()); + const makerTokenSymbol = currencyKeyMap.get(makerToken.toLowerCase()); + if (takerTokenSymbol === undefined || makerTokenSymbol === undefined) { + return []; + } + return this.getSynthetixSellQuotes( + readProxy, + takerTokenSymbol, + makerTokenSymbol, + takerFillAmounts, + ); + } default: throw new Error(`Unsupported sell sample source: ${source}`); } @@ -2068,6 +2141,21 @@ export class SamplerOperations { makerFillAmounts, ); } + case ERC20BridgeSource.Synthetix: { + const readProxy = SYNTHETIX_READ_PROXY_BY_CHAIN_ID[this.chainId]; + const currencyKeyMap = SYNTHETIX_CURRENCY_KEYS_BY_CHAIN_ID[this.chainId]; + const takerTokenSymbol = currencyKeyMap.get(takerToken.toLowerCase()); + const makerTokenSymbol = currencyKeyMap.get(makerToken.toLowerCase()); + if (takerTokenSymbol === undefined || makerTokenSymbol === undefined) { + return []; + } + return this.getSynthetixBuyQuotes( + readProxy, + takerTokenSymbol, + makerTokenSymbol, + makerFillAmounts, + ); + } default: throw new Error(`Unsupported buy sample source: ${source}`); } diff --git a/packages/asset-swapper/src/utils/market_operation_utils/types.ts b/packages/asset-swapper/src/utils/market_operation_utils/types.ts index 3e802bacb5..193a989823 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -1,3 +1,4 @@ +import { ChainId } from '@0x/contract-addresses'; import { FillQuoteTransformerLimitOrderInfo, FillQuoteTransformerOrderType, @@ -67,6 +68,7 @@ export enum ERC20BridgeSource { Compound = 'Compound', Synapse = 'Synapse', BancorV3 = 'BancorV3', + Synthetix = 'Synthetix', // BSC only PancakeSwap = 'PancakeSwap', PancakeSwapV2 = 'PancakeSwap_V2', @@ -381,6 +383,14 @@ export interface VelodromeFillData extends FillData { stable: boolean; } +export interface SynthetixFillData extends FillData { + synthetix: string; + takerTokenSymbolBytes32: string; + makerTokenSymbolBytes32: string; + // Only needed for gas estimation. + chainId: ChainId; +} + /** * Represents a node on a fill path. */ diff --git a/packages/asset-swapper/test/artifacts.ts b/packages/asset-swapper/test/artifacts.ts index d59142a8fa..b78346a3fb 100644 --- a/packages/asset-swapper/test/artifacts.ts +++ b/packages/asset-swapper/test/artifacts.ts @@ -43,6 +43,7 @@ import * as NativeOrderSampler from '../test/generated-artifacts/NativeOrderSamp import * as PlatypusSampler from '../test/generated-artifacts/PlatypusSampler.json'; import * as SamplerUtils from '../test/generated-artifacts/SamplerUtils.json'; import * as ShellSampler from '../test/generated-artifacts/ShellSampler.json'; +import * as SynthetixSampler from '../test/generated-artifacts/SynthetixSampler.json'; import * as TestNativeOrderSampler from '../test/generated-artifacts/TestNativeOrderSampler.json'; import * as TwoHopSampler from '../test/generated-artifacts/TwoHopSampler.json'; import * as UniswapSampler from '../test/generated-artifacts/UniswapSampler.json'; @@ -76,6 +77,7 @@ export const artifacts = { PlatypusSampler: PlatypusSampler as ContractArtifact, SamplerUtils: SamplerUtils as ContractArtifact, ShellSampler: ShellSampler as ContractArtifact, + SynthetixSampler: SynthetixSampler as ContractArtifact, TwoHopSampler: TwoHopSampler as ContractArtifact, UniswapSampler: UniswapSampler as ContractArtifact, UniswapV2Sampler: UniswapV2Sampler as ContractArtifact, diff --git a/packages/asset-swapper/test/wrappers.ts b/packages/asset-swapper/test/wrappers.ts index aab25f0bba..a987fc25b5 100644 --- a/packages/asset-swapper/test/wrappers.ts +++ b/packages/asset-swapper/test/wrappers.ts @@ -41,6 +41,7 @@ export * from '../test/generated-wrappers/native_order_sampler'; export * from '../test/generated-wrappers/platypus_sampler'; export * from '../test/generated-wrappers/sampler_utils'; export * from '../test/generated-wrappers/shell_sampler'; +export * from '../test/generated-wrappers/synthetix_sampler'; export * from '../test/generated-wrappers/test_native_order_sampler'; export * from '../test/generated-wrappers/two_hop_sampler'; export * from '../test/generated-wrappers/uniswap_sampler'; diff --git a/packages/asset-swapper/tsconfig.json b/packages/asset-swapper/tsconfig.json index c8016e3e82..18dea0a5b3 100644 --- a/packages/asset-swapper/tsconfig.json +++ b/packages/asset-swapper/tsconfig.json @@ -44,6 +44,7 @@ "test/generated-artifacts/PlatypusSampler.json", "test/generated-artifacts/SamplerUtils.json", "test/generated-artifacts/ShellSampler.json", + "test/generated-artifacts/SynthetixSampler.json", "test/generated-artifacts/TestNativeOrderSampler.json", "test/generated-artifacts/TwoHopSampler.json", "test/generated-artifacts/UniswapSampler.json", diff --git a/packages/protocol-utils/CHANGELOG.json b/packages/protocol-utils/CHANGELOG.json index 956122a78c..0e7f269d5d 100644 --- a/packages/protocol-utils/CHANGELOG.json +++ b/packages/protocol-utils/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "11.16.0", + "changes": [ + { + "note": "Add Synthetix support`", + "pr": 518 + } + ] + }, { "version": "11.15.0", "changes": [ diff --git a/packages/protocol-utils/src/transformer_utils.ts b/packages/protocol-utils/src/transformer_utils.ts index d587504547..f4bd1a96a9 100644 --- a/packages/protocol-utils/src/transformer_utils.ts +++ b/packages/protocol-utils/src/transformer_utils.ts @@ -140,6 +140,7 @@ export enum BridgeProtocol { Platypus, BancorV3, Velodrome, + Synthetix, } // tslint:enable: enum-naming