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
This commit is contained in:
Kevin Liao 2023-02-21 10:31:05 -05:00 committed by GitHub
parent 9f30823d70
commit dc8ff32d51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 344 additions and 8 deletions

@ -1 +1 @@
Subproject commit f8e700ed5d605f53f2194dc8df05a0bc3a7c1e43
Subproject commit a2edd39db95df7e9dd3f9ef9edc8c55fefddb6df

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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
})
);
}
}

View File

@ -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"
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -163,6 +163,7 @@ export enum BridgeProtocol {
Synthetix,
WOOFi,
AaveV3,
KyberElastic,
}
/**