From 72ba571ce3a00b4d1223e19e9a03924a284073c0 Mon Sep 17 00:00:00 2001 From: q1q0 Date: Tue, 13 Jun 2023 13:24:06 -0400 Subject: [PATCH] add kyber swap --- .../kyber/{ => kyber_v2}/events.sol | 0 .../kyber/{ => kyber_v2}/helpers.sol | 4 +- .../kyber/{ => kyber_v2}/interface.sol | 0 .../connectors/kyber/{ => kyber_v2}/main.sol | 2 +- .../connectors/kyber/kyber_v3/events.sol | 12 ++ .../connectors/kyber/kyber_v3/helpers.sol | 71 ++++++++ .../connectors/kyber/kyber_v3/interface.sol | 6 + .../connectors/kyber/kyber_v3/main.sol | 60 ++++++ test/mainnet/kyberswap/kyberswap.test.ts | 171 ++++++++++++++++++ 9 files changed, 323 insertions(+), 3 deletions(-) rename contracts/mainnet/connectors/kyber/{ => kyber_v2}/events.sol (100%) rename contracts/mainnet/connectors/kyber/{ => kyber_v2}/helpers.sol (79%) rename contracts/mainnet/connectors/kyber/{ => kyber_v2}/interface.sol (100%) rename contracts/mainnet/connectors/kyber/{ => kyber_v2}/main.sol (96%) create mode 100644 contracts/mainnet/connectors/kyber/kyber_v3/events.sol create mode 100644 contracts/mainnet/connectors/kyber/kyber_v3/helpers.sol create mode 100644 contracts/mainnet/connectors/kyber/kyber_v3/interface.sol create mode 100644 contracts/mainnet/connectors/kyber/kyber_v3/main.sol create mode 100644 test/mainnet/kyberswap/kyberswap.test.ts diff --git a/contracts/mainnet/connectors/kyber/events.sol b/contracts/mainnet/connectors/kyber/kyber_v2/events.sol similarity index 100% rename from contracts/mainnet/connectors/kyber/events.sol rename to contracts/mainnet/connectors/kyber/kyber_v2/events.sol diff --git a/contracts/mainnet/connectors/kyber/helpers.sol b/contracts/mainnet/connectors/kyber/kyber_v2/helpers.sol similarity index 79% rename from contracts/mainnet/connectors/kyber/helpers.sol rename to contracts/mainnet/connectors/kyber/kyber_v2/helpers.sol index 7c0e6f17..721228cd 100644 --- a/contracts/mainnet/connectors/kyber/helpers.sol +++ b/contracts/mainnet/connectors/kyber/kyber_v2/helpers.sol @@ -1,8 +1,8 @@ //SPDX-License-Identifier: MIT pragma solidity ^0.7.0; -import { DSMath } from "../../common/math.sol"; -import { Basic } from "../../common/basic.sol"; +import { DSMath } from "../../../common/math.sol"; +import { Basic } from "../../../common/basic.sol"; import { KyberInterface } from "./interface.sol"; abstract contract Helpers is DSMath, Basic { diff --git a/contracts/mainnet/connectors/kyber/interface.sol b/contracts/mainnet/connectors/kyber/kyber_v2/interface.sol similarity index 100% rename from contracts/mainnet/connectors/kyber/interface.sol rename to contracts/mainnet/connectors/kyber/kyber_v2/interface.sol diff --git a/contracts/mainnet/connectors/kyber/main.sol b/contracts/mainnet/connectors/kyber/kyber_v2/main.sol similarity index 96% rename from contracts/mainnet/connectors/kyber/main.sol rename to contracts/mainnet/connectors/kyber/kyber_v2/main.sol index 8b106565..ac81389f 100644 --- a/contracts/mainnet/connectors/kyber/main.sol +++ b/contracts/mainnet/connectors/kyber/kyber_v2/main.sol @@ -6,7 +6,7 @@ pragma solidity ^0.7.0; * @dev Decentralised Swapping. */ -import { TokenInterface } from "../../common/interfaces.sol"; +import { TokenInterface } from "../../../common/interfaces.sol"; import { Helpers } from "./helpers.sol"; import { Events } from "./events.sol"; diff --git a/contracts/mainnet/connectors/kyber/kyber_v3/events.sol b/contracts/mainnet/connectors/kyber/kyber_v3/events.sol new file mode 100644 index 00000000..dcba616e --- /dev/null +++ b/contracts/mainnet/connectors/kyber/kyber_v3/events.sol @@ -0,0 +1,12 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract Events { + event LogSwap( + address buyToken, + address sellToken, + uint256 buyAmt, + uint256 sellAmt, + uint256 setId + ); +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/kyber/kyber_v3/helpers.sol b/contracts/mainnet/connectors/kyber/kyber_v3/helpers.sol new file mode 100644 index 00000000..a278476e --- /dev/null +++ b/contracts/mainnet/connectors/kyber/kyber_v3/helpers.sol @@ -0,0 +1,71 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +import {DSMath} from "../../../common/math.sol"; +import {Basic} from "../../../common/basic.sol"; +import {TokenInterface} from "../../../common/interfaces.sol"; +import {AugustusSwapperInterface} from "./interface.sol"; + +abstract contract Helpers is DSMath, Basic { + struct SwapData { + TokenInterface sellToken; + TokenInterface buyToken; + uint256 _sellAmt; + uint256 _buyAmt; + uint256 unitAmt; + bytes callData; + } + + address internal constant kyberswap = + 0x6131B5fae19EA4f9D964eAc0408E4408b66337b5; + + function _swapHelper(SwapData memory swapData, uint256 wethAmt) + internal + returns (uint256 buyAmt) + { + TokenInterface buyToken = swapData.buyToken; + (uint256 _buyDec, uint256 _sellDec) = getTokensDec( + buyToken, + swapData.sellToken + ); + uint256 _sellAmt18 = convertTo18(_sellDec, swapData._sellAmt); + uint256 _slippageAmt = convert18ToDec( + _buyDec, + wmul(swapData.unitAmt, _sellAmt18) + ); + + uint256 initalBal = getTokenBal(buyToken); + + (bool success, ) = kyberswap.call{value: wethAmt}(swapData.callData); + if (!success) revert("kyberswap-failed"); + + uint256 finalBal = getTokenBal(buyToken); + + buyAmt = sub(finalBal, initalBal); + + require(_slippageAmt <= buyAmt, "Too much slippage"); + } + + function _swap(SwapData memory swapData, uint256 setId) + internal + returns (SwapData memory) + { + TokenInterface _sellAddr = swapData.sellToken; + + uint256 ethAmt; + + if (address(_sellAddr) == ethAddr) { + ethAmt = swapData._sellAmt; + } else { + address tokenProxy = AugustusSwapperInterface(kyberswap) + .getTokenTransferProxy(); + approve(TokenInterface(_sellAddr), tokenProxy, swapData._sellAmt); + } + + swapData._buyAmt = _swapHelper(swapData, ethAmt); + + setUint(setId, swapData._buyAmt); + + return swapData; + } +} diff --git a/contracts/mainnet/connectors/kyber/kyber_v3/interface.sol b/contracts/mainnet/connectors/kyber/kyber_v3/interface.sol new file mode 100644 index 00000000..7de1fbbf --- /dev/null +++ b/contracts/mainnet/connectors/kyber/kyber_v3/interface.sol @@ -0,0 +1,6 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +interface AugustusSwapperInterface { + function getTokenTransferProxy() external view returns (address); +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/kyber/kyber_v3/main.sol b/contracts/mainnet/connectors/kyber/kyber_v3/main.sol new file mode 100644 index 00000000..4b2b96dc --- /dev/null +++ b/contracts/mainnet/connectors/kyber/kyber_v3/main.sol @@ -0,0 +1,60 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +/** + * @title Kyber. + * @dev DEX Aggregator. + */ + +import {TokenInterface} from "../../../common/interfaces.sol"; +import {Stores} from "../../../common/stores.sol"; +import {Helpers} from "./helpers.sol"; + +abstract contract KyberResolver is Helpers { + /** + * @dev Sell ETH/ERC20_Token using KyberSwap. + * @notice Swap tokens from exchanges like kyber, 0x etc, with calculation done off-chain. + * @param buyAddr The address of the token to buy.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param sellAddr The address of the token to sell.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param sellAmt The amount of the token to sell. + * @param unitAmt The amount of buyAmt/sellAmt with slippage. + * @param callData Data from kyberswap API. + * @param setId ID stores the amount of token brought. + */ + function swap( + address buyAddr, + address sellAddr, + uint256 sellAmt, + uint256 unitAmt, + bytes calldata callData, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + Helpers.SwapData memory swapData = Helpers.SwapData({ + buyToken: TokenInterface(buyAddr), + sellToken: TokenInterface(sellAddr), + unitAmt: unitAmt, + callData: callData, + _sellAmt: sellAmt, + _buyAmt: 0 + }); + + swapData = _swap(swapData, setId); + + _eventName = "LogSwap(address,address,uint256,uint256,uint256)"; + _eventParam = abi.encode( + address(swapData.buyToken), + address(swapData.sellToken), + swapData._buyAmt, + swapData._sellAmt, + setId + ); + } +} + +contract ConnectV2KyberV3 is KyberResolver { + string public name = "Kyber-v3"; +} diff --git a/test/mainnet/kyberswap/kyberswap.test.ts b/test/mainnet/kyberswap/kyberswap.test.ts new file mode 100644 index 00000000..93831934 --- /dev/null +++ b/test/mainnet/kyberswap/kyberswap.test.ts @@ -0,0 +1,171 @@ +import hre from "hardhat"; +import { expect } from "chai"; +import { abis } from "../../../scripts/constant/abis"; +import { addresses } from "../../../scripts/tests/mainnet/addresses"; +import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector"; +import { getMasterSigner } from "../../../scripts/tests/getMasterSigner"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2"; +import { + ConnectV2KyberV3__factory, +} from "../../../typechain" +import { parseEther } from "@ethersproject/units"; +import { encodeSpells } from "../../../scripts/tests/encodeSpells"; +import { tokens } from "../../../scripts/tests/mainnet/tokens"; +import { constants } from "../../../scripts/constant/constant"; +import { addLiquidity } from "../../../scripts/tests/addLiquidity"; +import BigNumber from "bignumber.js"; +import axios from "axios"; +const { web3, deployments, waffle, ethers } = hre; +const { provider, deployContract } = waffle; +describe("Kyberswap", function() { + const connectorName = "kyberswap-test"; + let dsaWallet0: any; + let masterSigner: any; + let instaConnectorsV2: any; + let connector: any; + const wallets = provider.getWallets(); + const [wallet0, wallet1, wallet2, wallet3] = wallets; + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + // @ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url, + // blockNumber: 13300000, + }, + }, + ], + }); + masterSigner = await getMasterSigner(); + const erc20 = abis.basic.erc20; + instaConnectorsV2 = await ethers.getContractAt( + abis.core.connectorsV2, + addresses.core.connectorsV2 + ); + + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2KyberV3__factory, + signer: masterSigner, + connectors: instaConnectorsV2, + }); + // console.log("Connector address", connector.address); + }); + it("Should have contracts deployed.", async function() { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!masterSigner.address).to.be.true; + }); + describe("DSA wallet setup", function() { + it("Should build DSA v2", async function() { + dsaWallet0 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet0.address).to.be.true; + }); + it("Deposit ETH into DSA wallet", async function() { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10"), + }); + + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte( + ethers.utils.parseEther("10") + ); + }); + }); + describe("Main", function() { + it("should swap successfully", async function() { + async function getArg() { + const slippage = 1; + /* eth -> USDT */ + const sellTokenAddress = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; // eth, decimals 18 + const sellTokenDecimals = 18; + const buyTokenAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7"; // USDT, decimals 6 + const buyTokenDecimals = 6; + const amount = 2; + const srcAmount = new BigNumber(amount) + .times(new BigNumber(10).pow(sellTokenDecimals)) + .toFixed(0); + const fromAddress = dsaWallet0.address; + let url = `https://api.kyber.network/quote_amount`; + let params = { + base: sellTokenAddress, + quote: buyTokenAddress, + amount: srcAmount, + type: "sell", + }; + + const buyTokenAmount = await axios + .get(url, { params: params }) + .then((data) => data.data); + console.log("--------buy amount--------",buyTokenAmount) + let minAmount = new BigNumber(buyTokenAmount) + .times(1 - slippage / 100) + .toFixed(0); + + let txConfig = { + user_address: fromAddress, + src_id: sellTokenAddress, + dst_id: buyTokenAddress, + src_qty: srcAmount, + min_dst_qty: minAmount, + gas_price: 'high' + }; + let url2 = "https://api.kyber.network/trade_data"; + const calldata = await axios + .post(url2, txConfig) + .then((data) => data.data.data); + + function caculateUnitAmt( + buyAmount: any, + sellAmount: any, + buyDecimal: any, + sellDecimal: any, + maxSlippage: any + ) { + let unitAmt: any; + unitAmt = new BigNumber(buyAmount) + .dividedBy(10 ** buyDecimal) + .dividedBy(new BigNumber(sellAmount).dividedBy(10 ** sellDecimal)); + unitAmt = unitAmt.multipliedBy((100 - maxSlippage) / 100); + unitAmt = unitAmt.multipliedBy(1e18).toFixed(0); + return unitAmt; + } + + let unitAmt = caculateUnitAmt( + buyTokenAmount, + srcAmount, + buyTokenDecimals, + sellTokenDecimals, + 1 + ); + + return [ + buyTokenAddress, + sellTokenAddress, + srcAmount, + unitAmt, + calldata, + 0, + ]; + } + let arg = await getArg(); + const spells = [ + { + connector: connectorName, + method: "swap", + args: arg, + }, + ]; + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.lte( + ethers.utils.parseEther("8") + ); + }); + }); +});