Ethers.js – How to Modify ETH Price in Kyber Pool Using Hardhat Mainnet Fork?

decentralized-exchangeethers.js

I'm writing a Hardhat test where I trade in ETH for DAI on KyberSwap using Hardhat's mainnet fork, with the goal of altering Eth's price in the pool. The trade is successful since the test wallet receives DAI in the end, but unfortunately the price of ETH in the pool does not change.

From my test's output:

Initial ETH price was:  2567000000000000000000
ETH price after was:  2567000000000000000000

1 ETH should be enough to alter the price considering there's only 0.3 ETH in the pool at the time of writing. I even tried swapping 900 ETH, but still no change. Why is ETH's price on Kyber not affected?

Here's my test:

require('dotenv').config()
const { assert } = require("chai");
const { ethers } = require("hardhat");

// Contains ABi for balanceOf() method
const ERC20ABI = [{ "constant": true, "inputs": [{ "name": "_owner", "type": "address" }], "name": "balanceOf", "outputs": [{ "name": "balance", "type": "uint256" }], "payable": false, "stateMutability": "view", "type": "function" }]

// Contains ABI for Kyber's getExpectedRate() method
const kyberNetworkProxyABI = [{ "constant": true, "inputs": [{ "name": "src", "type": "address" }, { "name": "dest", "type": "address" }, { "name": "srcQty", "type": "uint256" }], "name": "getExpectedRate", "outputs": [{ "name": "expectedRate", "type": "uint256" }, { "name": "slippageRate", "type": "uint256" }], "payable": false, "stateMutability": "view", "type": "function" }]

const KYBER_NETWORK_PROXY_ADDRESS = "0x818E6FECD516Ecc3849DAf6845e3EC868087B755";
const KYBER_ETH_ADDRESS = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
const DAI_ADDRESS = "0x6b175474e89094c44da98b954eedeac495271d0f";

describe("kyber-eth-price-test", function () {
    it("Test that eth price is altered", async function () {
        const provider = ethers.provider;
        const [owner, addr1] = await ethers.getSigners();
        signer = await provider.getSigner(process.env.ETH_PUBLIC_KEY)

        // Get DAI contract
        const DAI = new ethers.Contract(DAI_ADDRESS, ERC20ABI, provider);

        // Get kyberNetworkProxy contract
        let kyberNetworkProxy = new ethers.Contract(
            KYBER_NETWORK_PROXY_ADDRESS,
            kyberNetworkProxyABI,
            signer,
        );

        // Deploy KyberTradeExample contract
        const kyberTradeExample =
            await ethers.getContractFactory("KyberTradeExample")
                .then(contract => contract.deploy(
                    KYBER_NETWORK_PROXY_ADDRESS,
                    DAI_ADDRESS
                ));
        await kyberTradeExample.deployed();

        // Assert addr1 DAI balance is initially 0
        addr1Dai = await DAI.balanceOf(addr1.address);
        assert(addr1Dai.isZero());

        // Get initial price from pegged block
        let ethPriceInitial
        const updateEthPrice = async () => {
            const results = await kyberNetworkProxy.getExpectedRate(
                KYBER_ETH_ADDRESS,
                DAI_ADDRESS,
                1 // 1 ETH
            )
            ethPriceInitial = results.expectedRate
        }
        await updateEthPrice()
        console.log("Initial ETH price was: ", ethPriceInitial.toString());

        // Swap 1 ETH for DAI
        await kyberTradeExample.connect(addr1).swapEthForDaiOnKyber(
            { value: ethers.utils.parseEther("999") }
        );

        // Assert DAI balance increased
        addr1Dai = await DAI.balanceOf(addr1.address);
        assert(addr1Dai.gt(ethers.BigNumber.from("0")));

        // Get Eth price after
        let ethPriceAfter
        const updateEthPrice2 = async () => {
            const results = await kyberNetworkProxy.getExpectedRate(
                KYBER_ETH_ADDRESS,
                DAI_ADDRESS,
                1 // 1 ETH
            )
            ethPriceAfter = results.expectedRate
        }
        await updateEthPrice2()
        console.log("ETH price after was: ", ethPriceAfter.toString());

        // Assert that Eth price has been altered
        assert.isFalse(ethPriceInitial.eq(ethPriceAfter), "Eth price did not change");
    });
});

Here's my solidity contract that I used to swap ETH for DAI from KyberSwap:

pragma solidity ^0.5.0;

import { KyberNetworkProxy as IKyberNetworkProxy } from '@studydefi/money-legos/kyber/contracts/KyberNetworkProxy.sol';
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract KyberTradeExample {
    IKyberNetworkProxy kyber;
    IERC20 dai;
    address constant KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    constructor(
        address kyberAddress,
        address daiAddress
    ) public {
      kyber = IKyberNetworkProxy(kyberAddress);
      dai = IERC20(daiAddress);
    }

    function swapEthForDaiOnKyber() external payable {
          (, uint minRate) = kyber.getExpectedRate(
            IERC20(KYBER_ETH_ADDRESS), 
            dai, 
            msg.value
          );

          // will send back tokens to this contract's address
          uint destAmount = kyber.swapEtherToToken.value(msg.value)(
            dai, 
            minRate
          );

          // send received tokens back to caller
          require(dai.transfer(msg.sender, destAmount));
    }

    function() external payable {}
}

In my hardhat.config.js I'm using Alchemy to create a fork and am pegging to block #14368910

module.exports = {
  solidity: "0.5.0",
  networks: {
    hardhat: {
      forking: {
        url: process.env.ALCHEMY_URL,
        blockNumber: 14368910
      }
    }
  }
};

UPDATE

I'm now fairly certain it's because Kyber is routing my trade through multiple DEX's. If I instead trade ETH for KNC, which directly uses the Kyber pool, my test will successfully alter the price of ETH.

So my question is now: is there a way I can request Kyber to avoid using multiple exchanges, and directly use Kyber's ETH/DAI pool?

Best Answer

After some reading I'm pretty sure this is not possible, since KyberSwap is designed to be a multi-DEX aggregator. From their docs:

KyberSwap trades are split and routed optimally through different DEXs for the best prices within the same chain/network.

Unless I missed something in the docs, turning off multi-DEX trading isn't an option, and so I don't think I'll be able to modify KyberSwap token prices by simply executing swaps.

Related Topic