From dc8ff32d511605436dd08363a3937ae918f3931d Mon Sep 17 00:00:00 2001 From: Kevin Liao Date: Tue, 21 Feb 2023 10:31:05 -0500 Subject: [PATCH] feat: Add Kyber Elastic mixin to Ethereum/Polygon/Arbitrum/Avalanche Bridge Adapters [LIT-753] (#661) * add kyber elastic to ethereum and polygon * add to arbitrum and avalanche * added kyber quoter/router dummy addresses * move kyberelastic test to a different file, fix some bugs with addresses, blocknumber * lint --- contracts/zero-ex/contracts/deps/forge-std | 2 +- .../bridges/ArbitrumBridgeAdapter.sol | 7 + .../bridges/AvalancheBridgeAdapter.sol | 7 + .../transformers/bridges/BridgeProtocols.sol | 1 + .../bridges/EthereumBridgeAdapter.sol | 7 + .../bridges/PolygonBridgeAdapter.sol | 7 + .../bridges/mixins/MixinKyberElastic.sol | 56 ++++++ .../tests/addresses/SourceAddresses.json | 35 +++- .../tests/forked/SwapERC20ForERC20Test.t.sol | 170 ++++++++++++++++++ contracts/zero-ex/tests/utils/ForkUtils.sol | 59 ++++++ .../protocol-utils/src/transformer_utils.ts | 1 + 11 files changed, 344 insertions(+), 8 deletions(-) create mode 100644 contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinKyberElastic.sol create mode 100644 contracts/zero-ex/tests/forked/SwapERC20ForERC20Test.t.sol diff --git a/contracts/zero-ex/contracts/deps/forge-std b/contracts/zero-ex/contracts/deps/forge-std index f8e700ed5d..a2edd39db9 160000 --- a/contracts/zero-ex/contracts/deps/forge-std +++ b/contracts/zero-ex/contracts/deps/forge-std @@ -1 +1 @@ -Subproject commit f8e700ed5d605f53f2194dc8df05a0bc3a7c1e43 +Subproject commit a2edd39db95df7e9dd3f9ef9edc8c55fefddb6df diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/ArbitrumBridgeAdapter.sol b/contracts/zero-ex/contracts/src/transformers/bridges/ArbitrumBridgeAdapter.sol index 8c91e3502c..cc974dde77 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/ArbitrumBridgeAdapter.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/ArbitrumBridgeAdapter.sol @@ -23,6 +23,7 @@ import "./mixins/MixinCurve.sol"; import "./mixins/MixinCurveV2.sol"; import "./mixins/MixinDodoV2.sol"; import "./mixins/MixinKyberDmm.sol"; +import "./mixins/MixinKyberElastic.sol"; import "./mixins/MixinGMX.sol"; import "./mixins/MixinNerve.sol"; import "./mixins/MixinUniswapV3.sol"; @@ -38,6 +39,7 @@ contract ArbitrumBridgeAdapter is MixinCurveV2, MixinDodoV2, MixinKyberDmm, + MixinKyberElastic, MixinGMX, MixinNerve, MixinUniswapV3, @@ -80,6 +82,11 @@ contract ArbitrumBridgeAdapter is return (0, true); } boughtAmount = _tradeKyberDmm(buyToken, sellAmount, order.bridgeData); + } else if (protocolId == BridgeProtocols.KYBERELASTIC) { + if (dryRun) { + return (0, true); + } + boughtAmount = _tradeKyberElastic(sellToken, sellAmount, order.bridgeData); } else if (protocolId == BridgeProtocols.UNISWAPV3) { if (dryRun) { return (0, true); diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/AvalancheBridgeAdapter.sol b/contracts/zero-ex/contracts/src/transformers/bridges/AvalancheBridgeAdapter.sol index cfe59f6811..dc193387fa 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/AvalancheBridgeAdapter.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/AvalancheBridgeAdapter.sol @@ -22,6 +22,7 @@ import "./mixins/MixinCurve.sol"; import "./mixins/MixinCurveV2.sol"; import "./mixins/MixinGMX.sol"; import "./mixins/MixinKyberDmm.sol"; +import "./mixins/MixinKyberElastic.sol"; import "./mixins/MixinAaveV2.sol"; import "./mixins/MixinNerve.sol"; import "./mixins/MixinPlatypus.sol"; @@ -36,6 +37,7 @@ contract AvalancheBridgeAdapter is MixinCurveV2, MixinGMX, MixinKyberDmm, + MixinKyberElastic, MixinAaveV2, MixinNerve, MixinPlatypus, @@ -78,6 +80,11 @@ contract AvalancheBridgeAdapter is return (0, true); } boughtAmount = _tradeKyberDmm(buyToken, sellAmount, order.bridgeData); + } else if (protocolId == BridgeProtocols.KYBERELASTIC) { + if (dryRun) { + return (0, true); + } + boughtAmount = _tradeKyberElastic(sellToken, sellAmount, order.bridgeData); } else if (protocolId == BridgeProtocols.AAVEV2) { if (dryRun) { return (0, true); diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol index 2baebcf54f..4eaa1e41a7 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol @@ -54,4 +54,5 @@ library BridgeProtocols { uint128 internal constant SYNTHETIX = 30; uint128 internal constant WOOFI = 31; uint128 internal constant AAVEV3 = 32; + uint128 internal constant KYBERELASTIC = 33; } diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/EthereumBridgeAdapter.sol b/contracts/zero-ex/contracts/src/transformers/bridges/EthereumBridgeAdapter.sol index af4e8dec32..9fc5be14ab 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/EthereumBridgeAdapter.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/EthereumBridgeAdapter.sol @@ -29,6 +29,7 @@ import "./mixins/MixinCryptoCom.sol"; import "./mixins/MixinDodo.sol"; import "./mixins/MixinDodoV2.sol"; import "./mixins/MixinKyberDmm.sol"; +import "./mixins/MixinKyberElastic.sol"; import "./mixins/MixinLido.sol"; import "./mixins/MixinMakerPSM.sol"; import "./mixins/MixinMStable.sol"; @@ -54,6 +55,7 @@ contract EthereumBridgeAdapter is MixinDodo, MixinDodoV2, MixinKyberDmm, + MixinKyberElastic, MixinLido, MixinMakerPSM, MixinMStable, @@ -165,6 +167,11 @@ contract EthereumBridgeAdapter is return (0, true); } boughtAmount = _tradeKyberDmm(buyToken, sellAmount, order.bridgeData); + } else if (protocolId == BridgeProtocols.KYBERELASTIC) { + if (dryRun) { + return (0, true); + } + boughtAmount = _tradeKyberElastic(sellToken, sellAmount, order.bridgeData); } else if (protocolId == BridgeProtocols.LIDO) { if (dryRun) { return (0, true); diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/PolygonBridgeAdapter.sol b/contracts/zero-ex/contracts/src/transformers/bridges/PolygonBridgeAdapter.sol index 2dc1c1cc57..a304546682 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/PolygonBridgeAdapter.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/PolygonBridgeAdapter.sol @@ -25,6 +25,7 @@ import "./mixins/MixinCurveV2.sol"; import "./mixins/MixinDodo.sol"; import "./mixins/MixinDodoV2.sol"; import "./mixins/MixinKyberDmm.sol"; +import "./mixins/MixinKyberElastic.sol"; import "./mixins/MixinMStable.sol"; import "./mixins/MixinNerve.sol"; import "./mixins/MixinSolidly.sol"; @@ -43,6 +44,7 @@ contract PolygonBridgeAdapter is MixinDodo, MixinDodoV2, MixinKyberDmm, + MixinKyberElastic, MixinMStable, MixinNerve, MixinUniswapV2, @@ -111,6 +113,11 @@ contract PolygonBridgeAdapter is return (0, true); } boughtAmount = _tradeKyberDmm(buyToken, sellAmount, order.bridgeData); + } else if (protocolId == BridgeProtocols.KYBERELASTIC) { + if (dryRun) { + return (0, true); + } + boughtAmount = _tradeKyberElastic(sellToken, sellAmount, order.bridgeData); } else if (protocolId == BridgeProtocols.AAVEV2) { if (dryRun) { return (0, true); diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinKyberElastic.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinKyberElastic.sol new file mode 100644 index 0000000000..66f08b0c5b --- /dev/null +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinKyberElastic.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + Copyright 2023 ZeroEx Intl. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-erc20/src/v06/LibERC20TokenV06.sol"; +import "@0x/contracts-erc20/src/IERC20Token.sol"; + +interface IKyberElasticRouter { + struct ExactInputParams { + bytes path; + address recipient; + uint256 deadline; + uint256 amountIn; + uint256 minAmountOut; + } + + function swapExactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut); +} + +contract MixinKyberElastic { + using LibERC20TokenV06 for IERC20Token; + + function _tradeKyberElastic( + IERC20Token sellToken, + uint256 sellAmount, + bytes memory bridgeData + ) internal returns (uint256 boughtAmount) { + (IKyberElasticRouter router, bytes memory path) = abi.decode(bridgeData, (IKyberElasticRouter, bytes)); + + // Grant the Kyber router an allowance to sell the sell token. + sellToken.approveIfBelow(address(router), sellAmount); + + boughtAmount = router.swapExactInput( + IKyberElasticRouter.ExactInputParams({ + path: path, + recipient: address(this), + deadline: block.timestamp, + amountIn: sellAmount, + minAmountOut: 1 + }) + ); + } +} diff --git a/contracts/zero-ex/tests/addresses/SourceAddresses.json b/contracts/zero-ex/tests/addresses/SourceAddresses.json index e0ad8e0965..b88fd41a2b 100644 --- a/contracts/zero-ex/tests/addresses/SourceAddresses.json +++ b/contracts/zero-ex/tests/addresses/SourceAddresses.json @@ -1,30 +1,51 @@ { "1": { "UniswapV2Router": "0xf164fc0ec4e93095b804a4795bbe1e041497b92a", - "UniswapV3Router": "0x0000000000000000000000000000000000000000" + "UniswapV3Router": "0x0000000000000000000000000000000000000000", + "KyberElasticQuoter": "0x0d125c15d54ca1f8a813c74a81aee34ebb508c1f", + "KyberElasticRouter": "0xc1e7dfe73e1598e3910ef4c7845b68a9ab6f4c83", + "KyberElasticPool": "0x952ffc4c47d66b454a8181f5c68b6248e18b66ec" }, "56": { "UniswapV2Router": "0x10ed43c718714eb63d5aa57b78b54704e256024e", - "UniswapV3Router": "0x0000000000000000000000000000000000000000" + "UniswapV3Router": "0x0000000000000000000000000000000000000000", + "KyberElasticQuoter": "0x0d125c15d54ca1f8a813c74a81aee34ebb508c1f", + "KyberElasticRouter": "0xc1e7dfe73e1598e3910ef4c7845b68a9ab6f4c83", + "KyberElasticPool": "0xfbfab68ba077d099cd4b66fa76920572cc0b557c" }, "137": { "UniswapV2Router": "0x1b02da8cb0d097eb8d57a175b88c7d8b47997506", - "UniswapV3Router": "0x0000000000000000000000000000000000000000" + "UniswapV3Router": "0x0000000000000000000000000000000000000000", + "KyberElasticQuoter": "0x0d125c15d54ca1f8a813c74a81aee34ebb508c1f", + "KyberElasticRouter": "0xc1e7dfe73e1598e3910ef4c7845b68a9ab6f4c83", + "KyberElasticPool": "0xf9cc934753a127100585812181ac04d07158a4c2" }, "43114": { "UniswapV2Router": "0x9Ad6C38BE94206cA50bb0d90783181662f0Cfa10", - "UniswapV3Router": "0x0000000000000000000000000000000000000000" + "UniswapV3Router": "0x0000000000000000000000000000000000000000", + "KyberElasticQuoter": "0x0d125c15d54ca1f8a813c74a81aee34ebb508c1f", + "KyberElasticRouter": "0xc1e7dfe73e1598e3910ef4c7845b68a9ab6f4c83", + "KyberElasticPool": "0x6038373de7f64da99b2a31951628b7d778b2c3cf" }, "250": { "UniswapV2Router": "0x1b02da8cb0d097eb8d57a175b88c7d8b47997506", - "UniswapV3Router": "0x0000000000000000000000000000000000000000" + "UniswapV3Router": "0x0000000000000000000000000000000000000000", + "KyberElasticQuoter": "0x0d125c15d54ca1f8a813c74a81aee34ebb508c1f", + "KyberElasticRouter": "0xc1e7dfe73e1598e3910ef4c7845b68a9ab6f4c83", + "KyberElasticPool": "0x8dcf5fed6ae6bf0befb5e4f0c9414c2cb9a4ed01" }, "10": { "UniswapV2Router": "0x0000000000000000000000000000000000000000", - "UniswapV3Router": "0x61ffe014ba17989e743c5f6cb21bf9697530b21e" + "UniswapV3Router": "0x61ffe014ba17989e743c5f6cb21bf9697530b21e", + "KyberElasticQuoter": "0x0d125c15d54ca1f8a813c74a81aee34ebb508c1f", + "KyberElasticRouter": "0xc1e7dfe73e1598e3910ef4c7845b68a9ab6f4c83", + "KyberElasticPool": "0x7e29ccaa4bf2894aca02c77e6b99cafc1d24b2f5" }, "42161": { "UniswapV2Router": "0x1b02da8cb0d097eb8d57a175b88c7d8b47997506", - "UniswapV3Router": "0x0000000000000000000000000000000000000000" + "UniswapV3Router": "0x0000000000000000000000000000000000000000", + "KyberElasticQuoter": "0x0d125c15d54ca1f8a813c74a81aee34ebb508c1f", + "KyberElasticRouter": "0xc1e7dfe73e1598e3910ef4c7845b68a9ab6f4c83", + "KyberElasticPool": "0x087abaab9cd85025a8b3916948c69fe173c837ea" } } diff --git a/contracts/zero-ex/tests/forked/SwapERC20ForERC20Test.t.sol b/contracts/zero-ex/tests/forked/SwapERC20ForERC20Test.t.sol new file mode 100644 index 0000000000..c743e811f8 --- /dev/null +++ b/contracts/zero-ex/tests/forked/SwapERC20ForERC20Test.t.sol @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + Copyright 2023 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; + +import "../utils/ForkUtils.sol"; +import "../utils/TestUtils.sol"; + +contract SwapERC20ForERC20Test is Test, ForkUtils, TestUtils { + function setUp() public { + string memory root = vm.projectRoot(); + string memory path = string(abi.encodePacked(root, "/", "tests/addresses/ContractAddresses.json")); + json = vm.readFile(path); + + for (uint256 i = 0; i < chains.length; i++) { + forkIds[chains[i]] = vm.createFork(vm.rpcUrl(chains[i])); + } + for (uint256 i = 0; i < chains.length; i++) { + chainsByChainId[chains[i]] = chainIds[i]; + indexChainsByChain[chains[i]] = indexChainIds[i]; + bytes memory details = json.parseRaw(indexChainIds[i]); + addresses = abi.decode(details, (ContractAddresses)); + } + } + + function test_swapERC20ForERC20OnKyberElastic() public { + for (uint256 i = 0; i < chains.length; i++) { + // kyberelastic mixin not deployed to these chains yet (bsc, fantom, optimism) + if (i == 1 || i == 4 || i == 5) { + continue; + } + vm.selectFork(forkIds[chains[i]]); + labelAddresses( + chains[i], + indexChainsByChain[chains[i]], + getTokens(i), + getContractAddresses(i), + getLiquiditySourceAddresses(i) + ); + swapOnKyberElastic(getTokens(i), getContractAddresses(i), getLiquiditySourceAddresses(i)); + } + } + + function swapOnKyberElastic( + TokenAddresses memory tokens, + ContractAddresses memory addresses, + LiquiditySources memory sources + ) public onlyForked { + if (sources.KyberElasticQuoter == address(0)) { + emit log_string("KyberElasticQuoter not available on this chain"); + return; + } + if (sources.KyberElasticRouter == address(0)) { + emit log_string("KyberElasticRouter not available on this chain"); + return; + } + if (sources.KyberElasticPool == address(0)) { + emit log_string("KyberElasticPool not available on this chain"); + return; + } + ITransformERC20Feature.Transformation[] memory transformations = new ITransformERC20Feature.Transformation[](2); + + transformations[0].deploymentNonce = _findTransformerNonce( + address(addresses.transformers.wethTransformer), + address(addresses.exchangeProxyTransformerDeployer) + ); + emit log_named_uint("WethTransformer nonce", transformations[0].deploymentNonce); + createNewFQT(tokens.WrappedNativeToken, addresses.exchangeProxy, addresses.exchangeProxyTransformerDeployer); + transformations[0].data = abi.encode(LibERC20Transformer.ETH_TOKEN_ADDRESS, 1e18); + transformations[1].deploymentNonce = _findTransformerNonce( + address(fillQuoteTransformer), + address(addresses.exchangeProxyTransformerDeployer) + ); + emit log_named_uint("FillQuoteTransformer nonce", transformations[1].deploymentNonce); + + FillQuoteTransformer.TransformData memory fqtData; + fqtData.side = FillQuoteTransformer.Side.Sell; + fqtData.sellToken = IERC20Token(address(tokens.USDC)); + fqtData.buyToken = IERC20Token(address(tokens.USDT)); + fqtData.fillSequence = new FillQuoteTransformer.OrderType[](1); + fqtData.fillSequence[0] = FillQuoteTransformer.OrderType.Bridge; + fqtData.fillAmount = 1e6; + + (uint256 amountOut, bytes memory path) = sampleKyberElastic( + fqtData.fillAmount, + address(fqtData.sellToken), + address(fqtData.buyToken), + sources.KyberElasticQuoter, + address(sources.KyberElasticPool) + ); + + log_named_uint("amountOut", amountOut); + + fqtData.bridgeOrders = new IBridgeAdapter.BridgeOrder[](1); + IBridgeAdapter.BridgeOrder memory order; + order.source = bytes32(uint256(BridgeProtocols.KYBERELASTIC) << 128); + order.takerTokenAmount = 1e6; + order.makerTokenAmount = amountOut; + order.bridgeData = abi.encode(address(sources.KyberElasticRouter), path); + fqtData.bridgeOrders[0] = order; + transformations[1].data = abi.encode(fqtData); + + vm.deal(address(this), 1e18); + uint256 balanceETHBefore = address(this).balance; + uint256 balanceERC20Before = IERC20Token(tokens.USDT).balanceOf(address(this)); + + writeTokenBalance(address(this), address(tokens.USDC), 1e16); + uint256 balanceUSDCbefore = IERC20Token(tokens.USDC).balanceOf(address(this)); + + IERC20Token(address(tokens.USDC)).approve(addresses.exchangeProxy, 1e16); + + IZeroEx(payable(addresses.exchangeProxy)).transformERC20{value: 1e18}( + // input token + IERC20Token(address(tokens.USDC)), + // output token + IERC20Token(address(tokens.USDT)), + // input token amount + 1e6, + // min output token amount + order.makerTokenAmount, + // list of transform + transformations + ); + + log_named_uint("NativeAsset balance before", balanceETHBefore); + log_named_uint("ERC-20 balance before", balanceERC20Before); + log_named_uint("NativeAsset balance after", balanceETHBefore - address(this).balance); + log_named_uint("ERC-20 balance after", IERC20Token(tokens.USDT).balanceOf(address(this)) - balanceERC20Before); + log_named_uint("USDC balance before", balanceUSDCbefore); + log_named_uint("USDC balance after", IERC20Token(tokens.USDT).balanceOf(address(tokens.USDC))); + assert(IERC20Token(tokens.USDT).balanceOf(address(this)) > 0); + } + + function sampleKyberElastic( + uint256 amount, + address takerToken, + address makerToken, + address quoter, + address pool + ) public returns (uint256 makerTokenAmount, bytes memory path) { + log_string(" Sampling KyberElastic for tokens"); + log_named_address(" ", takerToken); + log_string(" -> "); + log_named_address(" ", makerToken); + log_named_address(" quoter", quoter); + log_named_address("pool:", pool); + address[] memory tokenPath = new address[](2); + tokenPath[0] = address(takerToken); + tokenPath[1] = address(makerToken); + IKyberElasticQuoter kyberQuoter = IKyberElasticQuoter(quoter); + address[] memory poolPath = new address[](1); + poolPath[0] = address(pool); + path = _toKyberElasticPath(tokenPath, poolPath); + (uint256 amountOut, , , ) = kyberQuoter.quoteExactInput(path, amount); + return (amountOut, path); + } +} diff --git a/contracts/zero-ex/tests/utils/ForkUtils.sol b/contracts/zero-ex/tests/utils/ForkUtils.sol index c232d63747..4fef842678 100644 --- a/contracts/zero-ex/tests/utils/ForkUtils.sol +++ b/contracts/zero-ex/tests/utils/ForkUtils.sol @@ -89,6 +89,9 @@ struct TokenAddresses { } struct LiquiditySources { + address KyberElasticPool; + address KyberElasticQuoter; + address KyberElasticRouter; address UniswapV2Router; address UniswapV3Router; } @@ -97,6 +100,30 @@ interface IFQT { function bridgeAdapter() external returns (address); } +interface IKyberElasticQuoter { + function quoteExactInput( + bytes memory path, + uint256 amountIn + ) + external + returns ( + uint256 amountOut, + uint160[] memory afterSqrtPList, + uint32[] memory initializedTicksCrossedList, + uint256 gasEstimate + ); +} + +interface IKyberElasticPool { + function token0() external view returns (address); + + function token1() external view returns (address); + + /// @notice The fee to be charged for a swap in basis points + /// @return The swap fee in basis points + function swapFeeUnits() external view returns (uint24); +} + interface IUniswapV2Router01 { function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory amounts); @@ -715,6 +742,34 @@ contract ForkUtils is Test { } } + function _toKyberElasticPath( + address[] memory tokenPath, + address[] memory poolPath + ) internal returns (bytes memory path) { + require(tokenPath.length >= 2 && tokenPath.length == poolPath.length + 1, "invalid path lengths"); + // paths are tightly packed as: + // [token0, token0token1PairFee, token1, token1Token2PairFee, token2, ...] + path = new bytes(tokenPath.length * 20 + poolPath.length * 3); + uint256 o; + assembly { + o := add(path, 32) + } + for (uint256 i = 0; i < tokenPath.length; ++i) { + if (i > 0) { + uint24 poolFee = IKyberElasticPool(poolPath[i - 1]).swapFeeUnits(); + assembly { + mstore(o, shl(232, poolFee)) + o := add(o, 3) + } + } + address token = tokenPath[i]; + assembly { + mstore(o, shl(96, token)) + o := add(o, 20) + } + } + } + modifier onlyForked() { if (block.number >= 15000000) { _; @@ -722,4 +777,8 @@ contract ForkUtils is Test { revert("Requires fork mode"); } } + + function writeTokenBalance(address who, address token, uint256 amt) internal { + stdstore.target(token).sig(IERC20Token(token).balanceOf.selector).with_key(who).checked_write(amt); + } } diff --git a/packages/protocol-utils/src/transformer_utils.ts b/packages/protocol-utils/src/transformer_utils.ts index dd7cc46389..4d3759533b 100644 --- a/packages/protocol-utils/src/transformer_utils.ts +++ b/packages/protocol-utils/src/transformer_utils.ts @@ -163,6 +163,7 @@ export enum BridgeProtocol { Synthetix, WOOFi, AaveV3, + KyberElastic, } /**