Skip to content

Commit

Permalink
connectors: implement socket bridge connector
Browse files Browse the repository at this point in the history
  • Loading branch information
facuspagnuolo committed Sep 11, 2024
1 parent 5ecd49b commit 4b72e33
Show file tree
Hide file tree
Showing 14 changed files with 373 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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;

/**
* @title Socket connector interface
*/
interface ISocketConnector {
/**
* @dev The post token balance is lower than the previous token balance minus the amount bridged
*/
error SocketBridgeBadPostTokenBalance(uint256 postBalance, uint256 preBalance, uint256 amount);

/**
* @dev Tells the reference to the Socket gateway of the source chain
*/
function socketGateway() external view returns (address);

/**
* @dev Executes a bridge of assets using Socket
* @param token Address of the token to be bridged
* @param amount Amount of tokens to be bridged
* @param data Data to be sent to the socket gateway
*/
function execute(address token, uint256 amount, bytes memory data) external;
}
54 changes: 54 additions & 0 deletions packages/connectors/contracts/socket/SocketConnector.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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/utils/Address.sol';

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

import '../interfaces/socket/ISocketConnector.sol';

/**
* @title SocketConnector
* @dev Interfaces with Socket to bridge tokens
*/
contract SocketConnector is ISocketConnector {
// Reference to the Socket gateway of the source chain
address public immutable override socketGateway;

/**
* @dev Creates a new Socket connector
* @param _socketGateway Address of the Socket gateway for the source chain
*/
constructor(address _socketGateway) {
socketGateway = _socketGateway;
}

/**
* @dev Executes a bridge of assets using Socket
* @param token Address of the token to be bridged
* @param amount Amount of tokens to be bridged
* @param data Data to be sent to the Socket gateway
*/
function execute(address token, uint256 amount, bytes memory data) external override {
uint256 preBalance = IERC20(token).balanceOf(address(this));
ERC20Helpers.approve(token, socketGateway, amount);
Address.functionCall(socketGateway, data, 'SOCKET_BRIDGE_FAILED');

uint256 postBalance = IERC20(token).balanceOf(address(this));
bool isPostBalanceUnexpected = postBalance < preBalance - amount;
if (isPostBalanceUnexpected) revert SocketBridgeBadPostTokenBalance(postBalance, preBalance, amount);
}
}
2 changes: 1 addition & 1 deletion packages/connectors/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"test:arbitrum": "yarn test --fork arbitrum --block-number 212259071 --chain-id 42161",
"test:gnosis": "yarn test --fork gnosis --block-number 28580764 --chain-id 100",
"test:avalanche": "yarn test --fork avalanche --block-number 31333905 --chain-id 43114",
"test:bsc": "yarn test --fork bsc --block-number 41410900 --chain-id 56",
"test:bsc": "yarn test --fork bsc --block-number 42144988 --chain-id 56",
"test:fantom": "yarn test --fork fantom --block-number 61485606 --chain-id 250",
"test:zkevm": "yarn test --fork zkevm --block-number 9014946 --chain-id 1101",
"test:base": "yarn test --fork base --block-number 18341220 --chain-id 8453",
Expand Down
73 changes: 73 additions & 0 deletions packages/connectors/src/socket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import axios, { AxiosError } from 'axios'
import { BigNumber, Contract } from 'ethers'

const SOCKET_URL = 'https://api.socket.tech/v2'
const SOCKET_API_KEY = '72a5b4b0-e727-48be-8aa1-5da9d62fe635'

/* eslint-disable @typescript-eslint/no-explicit-any */

export type QuoteResponse = { data: { result: { routes: any[] } } }
export type TransactionDataResponse = { data: { result: { txData: string } } }

export async function getSocketBridgeData(
sender: Contract,
fromChainId: number,
fromToken: Contract,
fromAmount: BigNumber,
toChainId: number,
toToken: Contract,
slippage: number
): Promise<string> {
try {
const quote = await getQuote(sender, fromChainId, fromToken, fromAmount, toChainId, toToken, slippage)
const transaction = await getTransactionData(quote.data.result.routes[0])
return transaction.data.result.txData
} catch (error) {
if (error instanceof AxiosError) throw Error(error.toString() + ' - ' + error.response?.data?.description)
else throw error
}
}

async function getQuote(
sender: Contract,
fromChainId: number,
fromToken: Contract,
fromAmount: BigNumber,
toChainId: number,
toToken: Contract,
slippage: number
): Promise<QuoteResponse> {
return axios.get(`${SOCKET_URL}/quote`, {
headers: {
'API-KEY': SOCKET_API_KEY,
Accept: 'application/json',
},
params: {
userAddress: sender.address,
fromChainId: fromChainId,
fromTokenAddress: fromToken.address,
fromAmount: fromAmount.toString(),
toChainId: toChainId,
toTokenAddress: toToken.address,
defaultBridgeSlippage: slippage < 1 ? slippage * 100 : slippage,
singleTxOnly: true,
uniqueRoutesPerBridge: true,
sort: 'output',
includeDexes: ['oneinch', 'rainbow'],
includeBridges: ['cctp', 'celer', 'connext', 'hop', 'stargate'],
},
})
}

async function getTransactionData(route: any): Promise<TransactionDataResponse> {
return axios.post(
`${SOCKET_URL}/build-tx`,
{ route },
{
headers: {
'API-KEY': SOCKET_API_KEY,
Accept: 'application/json',
},
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function itBehavesLikeConnextConnector(
})

context('when the recipient is not the zero address', async () => {
const slippage = 0.05
const slippage = 0.15
const relayerFee = amount.div(10)

let minAmountOut: BigNumber, amountAfterFees: BigNumber
Expand Down
7 changes: 1 addition & 6 deletions packages/connectors/test/connext/ConnextConnector.bsc.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { deploy, fp, toUSDC } from '@mimic-fi/v3-helpers'
import { deploy, toUSDC } from '@mimic-fi/v3-helpers'

import { itBehavesLikeConnextConnector } from './ConnextConnector.behavior'

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

const USDC = '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d'
const WETH = '0x2170Ed0880ac9A755fd29B2688956BD959F933F8'
const WHALE = '0x8894e0a0c962cb723c1976a4421c95949be2d4e3'

const CONNEXT = '0xCd401c10afa37d641d2F594852DA94C700e4F2CE'
Expand All @@ -20,8 +19,4 @@ describe('ConnextConnector', () => {
context('USDC', () => {
itBehavesLikeConnextConnector(SOURCE_CHAIN_ID, USDC, toUSDC(300), CONNEXT, WHALE)
})

context('WETH', () => {
itBehavesLikeConnextConnector(SOURCE_CHAIN_ID, WETH, fp(0.5), CONNEXT, WHALE)
})
})
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { deploy, fp, toUSDC } from '@mimic-fi/v3-helpers'
import { deploy, toUSDC } from '@mimic-fi/v3-helpers'

import { itBehavesLikeConnextConnector } from './ConnextConnector.behavior'

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

const USDC = '0x7F5c764cBc14f9669B88837ca1490cCa17c31607'
const WETH = '0x4200000000000000000000000000000000000006'
const WHALE = '0x85149247691df622eaf1a8bd0cafd40bc45154a9'

const CONNEXT = '0x8f7492DE823025b4CfaAB1D34c58963F2af5DEDA'
Expand All @@ -20,8 +19,4 @@ describe('ConnextConnector', () => {
context('USDC', () => {
itBehavesLikeConnextConnector(SOURCE_CHAIN_ID, USDC, toUSDC(300), CONNEXT, WHALE)
})

context('WETH', () => {
itBehavesLikeConnextConnector(SOURCE_CHAIN_ID, WETH, fp(2), CONNEXT, WHALE)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"fromChainId": 56,
"fromToken": "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
"fromAmount": "50000000000000000000000",
"toChainId": 1,
"toToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"slippage": 0.02,
"data": "0x0000000d52106ce9345fa8d86eeffdd25c389441ff96e05cf90592de8ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000a968163f0a57b4000000000000100b8d397000068dc"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"fromChainId": 56,
"fromToken": "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
"fromAmount": "50000000000000000000000",
"toChainId": 10,
"toToken": "0x7F5c764cBc14f9669B88837ca1490cCa17c31607",
"slippage": 0.02,
"data": "0x0000000d52106ce9345fa8d86eeffdd25c389441ff96e05cf90592de8ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000a968163f0a57b4000000000000a00b8e5120000620c"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"fromChainId": 56,
"fromToken": "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
"fromAmount": "50000000000000000000000",
"toChainId": 1,
"toToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"slippage": 0.02,
"data": "0x0000000d52106ce94a1bf945c2995b53a00df694fab85f453e5e1f5e8ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000a968163f0a57b4000000000000100d2437d000068dd"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"fromChainId": 56,
"fromToken": "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
"fromAmount": "50000000000000000000000",
"toChainId": 10,
"toToken": "0x7F5c764cBc14f9669B88837ca1490cCa17c31607",
"slippage": 0.02,
"data": "0x0000000d52106ce94a1bf945c2995b53a00df694fab85f453e5e1f5e8ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000a968163f0a57b4000000000000a00d256670000620c"
}
84 changes: 84 additions & 0 deletions packages/connectors/test/helpers/socket/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { currentBlockNumber } from '@mimic-fi/v3-helpers'
import { BigNumber, Contract } from 'ethers'
import fs from 'fs'
import hre from 'hardhat'
import { HardhatNetworkConfig } from 'hardhat/types'
import path from 'path'

import { getSocketBridgeData } from '../../../src/socket'

type Fixture = {
fromChainId: number
fromToken: string
fromAmount: string
toChainId: number
toToken: string
slippage: number
data: string
}

export async function loadOrGetSocketData(
sender: Contract,
fromChainId: number,
fromToken: Contract,
fromAmount: BigNumber,
toChainId: number,
toToken: Contract,
slippage: number
): Promise<string> {
const config = hre.network.config as HardhatNetworkConfig
const blockNumber = config?.forking?.blockNumber?.toString() || (await currentBlockNumber()).toString()

const fixture = await readFixture(fromChainId, fromToken, toChainId, toToken, blockNumber)
if (fixture) return fixture.data

const data = await getSocketBridgeData(sender, fromChainId, fromToken, fromAmount, toChainId, toToken, slippage)
await saveFixture(fromChainId, fromToken, fromAmount, toChainId, toToken, slippage, data, blockNumber)
return data
}

async function readFixture(
fromChainId: number,
fromToken: Contract,
toChainId: number,
toToken: Contract,
blockNumber: string
): Promise<Fixture | undefined> {
const bridgePath = `${await fromToken.symbol()}-${toChainId}.json`
const fixturePath = path.join(__dirname, 'fixtures', fromChainId.toString(), blockNumber, bridgePath)
if (!fs.existsSync(fixturePath)) return undefined
return JSON.parse(fs.readFileSync(fixturePath).toString())
}

async function saveFixture(
fromChainId: number,
fromToken: Contract,
fromAmount: BigNumber,
toChainId: number,
toToken: Contract,
slippage: number,
data: string,
blockNumber: string
): Promise<void> {
const output = {
fromChainId: fromChainId,
fromToken: fromToken.address,
fromAmount: fromAmount.toString(),
toChainId: toChainId,
toToken: toToken.address,
slippage,
data,
}

const fixturesPath = path.join(__dirname, 'fixtures')
if (!fs.existsSync(fixturesPath)) fs.mkdirSync(fixturesPath)

const networkPath = path.join(fixturesPath, fromChainId.toString())
if (!fs.existsSync(networkPath)) fs.mkdirSync(networkPath)

const blockNumberPath = path.join(networkPath, blockNumber)
if (!fs.existsSync(blockNumberPath)) fs.mkdirSync(blockNumberPath)

const bridgePath = path.join(blockNumberPath, `${await fromToken.symbol()}-${toChainId}.json`)
fs.writeFileSync(bridgePath, JSON.stringify(output, null, 2))
}
Loading

0 comments on commit 4b72e33

Please sign in to comment.