Skip to content

Commit

Permalink
Connectors: Implement Hop swap connector (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
facuspagnuolo authored Jun 26, 2023
1 parent 597b23c commit edcbf48
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 57 deletions.
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

0 comments on commit edcbf48

Please sign in to comment.