Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connectors: Implement Hop swap connector #17

Merged
merged 10 commits into from
Jun 26, 2023
2 changes: 2 additions & 0 deletions .github/workflows/ci-connectors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,5 @@ jobs:
run: yarn workspace @mimic-fi/v3-connectors test:optimism
- name: Test arbitrum
run: yarn workspace @mimic-fi/v3-connectors test:arbitrum
- name: Test gnosis
run: yarn workspace @mimic-fi/v3-connectors test:gnosis
60 changes: 60 additions & 0 deletions packages/connectors/contracts/swap/hop/HopSwapConnector.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.8.0;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';

import '@mimic-fi/v3-helpers/contracts/utils/ERC20Helpers.sol';

import './IHopDex.sol';

/**
* @title HopSwapConnector
* @dev Interfaces with Hop to swap tokens
*/
contract HopSwapConnector {
/**
* @dev Executes a token swap in Hop
* @param tokenIn Token being sent
* @param tokenOut Token being received
* @param amountIn Amount of tokenIn being swapped
* @param minAmountOut Minimum amount of tokenOut willing to receive
* @param hopDexAddress Address of the Hop dex to be used
*/
function execute(address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, address hopDexAddress)
external
returns (uint256 amountOut)
{
require(tokenIn != tokenOut, 'HOP_SWAP_SAME_TOKEN');
require(hopDexAddress != address(0), 'HOP_DEX_ADDRESS_ZERO');

uint256 preBalanceIn = IERC20(tokenIn).balanceOf(address(this));
uint256 preBalanceOut = IERC20(tokenOut).balanceOf(address(this));

IHopDex hopDex = IHopDex(hopDexAddress);
uint8 tokenInIndex = hopDex.getTokenIndex(tokenIn);
uint8 tokenOutIndex = hopDex.getTokenIndex(tokenOut);

ERC20Helpers.approve(tokenIn, hopDexAddress, amountIn);
hopDex.swap(tokenInIndex, tokenOutIndex, amountIn, minAmountOut, block.timestamp);

uint256 postBalanceIn = IERC20(tokenIn).balanceOf(address(this));
require(postBalanceIn >= preBalanceIn - amountIn, 'HOP_BAD_TOKEN_IN_BALANCE');

uint256 postBalanceOut = IERC20(tokenOut).balanceOf(address(this));
amountOut = postBalanceOut - preBalanceOut;
require(amountOut >= minAmountOut, 'HOP_MIN_AMOUNT_OUT');
}
}
23 changes: 23 additions & 0 deletions packages/connectors/contracts/swap/hop/IHopDex.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

pragma solidity >=0.8.0;

interface IHopDex {
function getTokenIndex(address) external view returns (uint8);

function swap(uint8 tokenIndexFrom, uint8 tokenIndexTo, uint256 dx, uint256 minDy, uint256 deadline)
external
returns (uint256);
}
1 change: 1 addition & 0 deletions packages/connectors/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"test:polygon": "yarn test --fork polygon --block-number 44153231",
"test:optimism": "yarn test --fork optimism --block-number 105914596",
"test:arbitrum": "yarn test --fork arbitrum --block-number 105116582",
"test:gnosis": "yarn test --fork gnosis --block-number 28580764",
"prepare": "yarn build"
},
"dependencies": {
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

22 changes: 22 additions & 0 deletions packages/connectors/test/swap/hop/HopSwapConnector.arbitrum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { deploy } from '@mimic-fi/v3-helpers'

import { itBehavesLikeHopSwapConnector } from './HopSwapConnector.behavior'

/* eslint-disable no-secrets/no-secrets */

const CHAIN = 42161

const USDC = '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8'
const HUSDC = '0x0ce6c85cf43553de10fc56ceca0aef6ff0dd444d'
const WHALE = '0x5bdf85216ec1e38d6458c870992a69e38e03f7ef'
const HOP_USDC_DEX = '0x10541b07d8ad2647dc6cd67abd4c03575dade261'

describe('HopSwapConnector', () => {
const SLIPPAGE = 0.01

before('create hop swap connector', async function () {
this.connector = await deploy('HopSwapConnector')
})

itBehavesLikeHopSwapConnector(CHAIN, USDC, HUSDC, HOP_USDC_DEX, WHALE, SLIPPAGE)
})
55 changes: 55 additions & 0 deletions packages/connectors/test/swap/hop/HopSwapConnector.behavior.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { fp, impersonate, instanceAt, toUSDC } from '@mimic-fi/v3-helpers'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { expect } from 'chai'
import { BigNumber, Contract } from 'ethers'

export function itBehavesLikeHopSwapConnector(
CHAIN: number,
USDC: string,
HUSDC: string,
HOP_USDC_DEX: string,
WHALE: string,
SLIPPAGE: number
): void {
let usdc: Contract, husdc: Contract, whale: SignerWithAddress

before('load tokens and accounts', async function () {
usdc = await instanceAt('IERC20Metadata', USDC)
husdc = await instanceAt('IERC20Metadata', HUSDC)
whale = await impersonate(WHALE, fp(100))
})

context('USDC-hUSDC', () => {
const amountIn = toUSDC(10e3)

it('swaps correctly', async function () {
const previousBalance = await husdc.balanceOf(this.connector.address)
await usdc.connect(whale).transfer(this.connector.address, amountIn)

await this.connector.connect(whale).execute(USDC, HUSDC, amountIn, 0, HOP_USDC_DEX)

const currentBalance = await husdc.balanceOf(this.connector.address)
const expectedMinAmountOut = amountIn.sub(amountIn.mul(fp(SLIPPAGE)).div(fp(1)))
expect(currentBalance.sub(previousBalance)).to.be.at.least(expectedMinAmountOut)
})
})

context('hUSDC-USDC', () => {
let amountIn: BigNumber

beforeEach('load amount in', async function () {
amountIn = await husdc.balanceOf(this.connector.address)
expect(amountIn).to.be.gt(0)
})

it('swaps correctly hUSDC-USDC', async function () {
const previousBalance = await usdc.balanceOf(this.connector.address)

await this.connector.connect(whale).execute(HUSDC, USDC, amountIn, 0, HOP_USDC_DEX)

const currentBalance = await usdc.balanceOf(this.connector.address)
const expectedMinAmountOut = amountIn.sub(amountIn.mul(fp(SLIPPAGE)).div(fp(1)))
expect(currentBalance.sub(previousBalance)).to.be.at.least(expectedMinAmountOut)
})
})
}
22 changes: 22 additions & 0 deletions packages/connectors/test/swap/hop/HopSwapConnector.gnosis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { deploy } from '@mimic-fi/v3-helpers'

import { itBehavesLikeHopSwapConnector } from './HopSwapConnector.behavior'

/* eslint-disable no-secrets/no-secrets */

const CHAIN = 100

const USDC = '0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83'
const HUSDC = '0x9ec9551d4a1a1593b0ee8124d98590cc71b3b09d'
const WHALE = '0xc66825c5c04b3c2ccd536d626934e16248a63f68'
const HOP_USDC_SWAP = '0x5c32143c8b198f392d01f8446b754c181224ac26'

describe('HopSwapConnector', () => {
const SLIPPAGE = 0.02

before('create hop swap connector', async function () {
this.connector = await deploy('HopSwapConnector')
})

itBehavesLikeHopSwapConnector(CHAIN, USDC, HUSDC, HOP_USDC_SWAP, WHALE, SLIPPAGE)
})
Loading