Skip to content

Commit

Permalink
Merge pull request #3 from chainhop-dex/fix-susd
Browse files Browse the repository at this point in the history
support non standard underlying_coins(uint256) impl
  • Loading branch information
padoriku authored Apr 19, 2022
2 parents 4b9fa2e + 2e1f301 commit 6648d7e
Show file tree
Hide file tree
Showing 10 changed files with 89 additions and 51 deletions.
26 changes: 3 additions & 23 deletions contracts/codecs/CurveMetaPoolCodec.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ pragma solidity >=0.8.12;
import "@openzeppelin/contracts/access/Ownable.sol";
import "../interfaces/ICodec.sol";
import "../interfaces/ICurvePool.sol";
import "./CurveTokenAddresses.sol";

contract CurveMetaPoolCodec is ICodec, Ownable {
contract CurveMetaPoolCodec is ICodec, CurveTokenAddresses {
struct SwapCalldata {
int128 i;
int128 j;
Expand All @@ -15,28 +16,7 @@ contract CurveMetaPoolCodec is ICodec, Ownable {
address _receiver;
}

event PoolTokensSet(address[] pools, address[][] poolTokens);

// Pool address to *underlying* token addresses. position sensitive.
// This is needed because some of the metapools fail to implement curve's underlying_coins() spec,
// therefore no consistant way to query token addresses by their indices.
mapping(address => address[]) public poolToTokens;

constructor(address[] memory _pools, address[][] memory _poolTokens) {
_setPoolTokens(_pools, _poolTokens);
}

function setPoolTokens(address[] calldata _pools, address[][] calldata _poolTokens) external onlyOwner {
_setPoolTokens(_pools, _poolTokens);
}

function _setPoolTokens(address[] memory _pools, address[][] memory _poolTokens) private {
require(_pools.length == _poolTokens.length, "len mm");
for (uint256 i = 0; i < _pools.length; i++) {
poolToTokens[_pools[i]] = _poolTokens[i];
}
emit PoolTokensSet(_pools, _poolTokens);
}
constructor(address[] memory _pools, address[][] memory _poolTokens) CurveTokenAddresses(_pools, _poolTokens) {}

function decodeCalldata(ICodec.SwapDescription calldata _swap)
external
Expand Down
24 changes: 19 additions & 5 deletions contracts/codecs/CurveSpecialMetaPoolCodec.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,24 @@ pragma solidity >=0.8.12;
import "@openzeppelin/contracts/access/Ownable.sol";
import "../interfaces/ICodec.sol";
import "../interfaces/ICurvePool.sol";
import "./CurveTokenAddresses.sol";

/**
* @title a special codec for a pool that implements exchange_underlying() differently than all others.
* i.e. the "sUSD" pool on Ethereum
* @title a special codec for pools that implement exchange_underlying() slightly differently than others.
* e.g. "sUSD" pool on Ethereum and "aave" on Polygon
* @author padoriku
* @notice encode/decode calldata
*/
contract CurveSpecialMetaPoolCodec is ICodec, Ownable {
contract CurveSpecialMetaPoolCodec is ICodec, CurveTokenAddresses {
struct SwapCalldata {
int128 i;
int128 j;
uint256 dx;
uint256 min_dy;
}

constructor(address[] memory _pools, address[][] memory _poolTokens) CurveTokenAddresses(_pools, _poolTokens) {}

function decodeCalldata(ICodec.SwapDescription calldata _swap)
external
view
Expand All @@ -31,8 +34,19 @@ contract CurveSpecialMetaPoolCodec is ICodec, Ownable {
{
SwapCalldata memory data = abi.decode((_swap.data[4:]), (SwapCalldata));
amountIn = data.dx;
tokenIn = ICurvePool(_swap.dex).underlying_coins(uint128(data.i));
tokenOut = ICurvePool(_swap.dex).underlying_coins(uint128(data.j));
uint256 i = uint256(uint128(data.i));
uint256 j = uint256(uint128(data.j));

address[] memory tokens = poolToTokens[_swap.dex];
if (tokens.length > 0) {
// some pool(sUSD)'s implementation of underlying_coins takes uint128 instead of uint256 as input
// register these pool's token addresses manually to workaround this.
tokenIn = tokens[i];
tokenOut = tokens[j];
} else {
tokenIn = ICurvePool(_swap.dex).underlying_coins(i);
tokenOut = ICurvePool(_swap.dex).underlying_coins(j);
}
}

function encodeCalldataWithOverride(
Expand Down
30 changes: 30 additions & 0 deletions contracts/codecs/CurveTokenAddresses.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: GPL-3.0-only

pragma solidity >=0.8.12;

import "@openzeppelin/contracts/access/Ownable.sol";

contract CurveTokenAddresses is Ownable {
event PoolTokensSet(address[] pools, address[][] poolTokens);

// Pool address to *underlying* token addresses. position sensitive.
// This is needed because some of the metapools fail to implement curve's underlying_coins() spec,
// therefore no consistant way to query token addresses by their indices.
mapping(address => address[]) public poolToTokens;

constructor(address[] memory _pools, address[][] memory _poolTokens) {
_setPoolTokens(_pools, _poolTokens);
}

function setPoolTokens(address[] calldata _pools, address[][] calldata _poolTokens) external onlyOwner {
_setPoolTokens(_pools, _poolTokens);
}

function _setPoolTokens(address[] memory _pools, address[][] memory _poolTokens) private {
require(_pools.length == _poolTokens.length, "len mm");
for (uint256 i = 0; i < _pools.length; i++) {
poolToTokens[_pools[i]] = _poolTokens[i];
}
emit PoolTokensSet(_pools, _poolTokens);
}
}
3 changes: 1 addition & 2 deletions contracts/interfaces/ICurvePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ pragma solidity >=0.8.12;
interface ICurvePool {
function coins(uint256 i) external view returns (address);

// specifically for CurveNonStandardMetaPoolCodec, the uint128 not used in other codecs
function underlying_coins(uint128 i) external view returns (address);
function underlying_coins(uint256 i) external view returns (address);

// plain & meta pool
function get_dy(
Expand Down
7 changes: 5 additions & 2 deletions deploy/codecs/CurveSpecialMetaPoolCodec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as dotenv from 'dotenv';
import { DeployFunction } from 'hardhat-deploy/types';
import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { verify } from '../configs/functions';
import { getSpecialMetaPoolCodecConfig, verify } from './../configs/functions';

dotenv.config();

Expand All @@ -10,9 +10,12 @@ const deployFunc: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const { deploy } = deployments;
const { deployer } = await getNamedAccounts();

const chainId = parseInt(await hre.getChainId());

const result = await deploy('CurveSpecialMetaPoolCodec', {
from: deployer,
log: true
log: true,
args: getSpecialMetaPoolCodecConfig(chainId).args
});
await verify(hre, result);
};
Expand Down
16 changes: 5 additions & 11 deletions deploy/configs/config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import * as dotenv from 'dotenv';
import { getMetaPoolCodecConfig, getSupportedCurvePools } from './functions';
import {
CurvePoolCodec,
CurveSpecialMetaPoolCodec,
IConfig,
UniswapV2SwapExactTokensForTokensCodec,
UniswapV3ExactInputCodec
} from './types';
import { getMetaPoolCodecConfig, getSpecialMetaPoolCodecConfig, getSupportedCurvePools } from './functions';
import { CurvePoolCodec, IConfig, UniswapV2SwapExactTokensForTokensCodec, UniswapV3ExactInputCodec } from './types';

dotenv.config();

Expand All @@ -22,7 +16,7 @@ export const deploymentConfigs: IConfig = {
{ address: '0xE592427A0AEce92De3Edee1F18E0157C05861564', func: UniswapV3ExactInputCodec.func }, // UniswapV3: SwapRouter
...getSupportedCurvePools(1)
],
codecs: [UniswapV3ExactInputCodec, CurvePoolCodec, CurveSpecialMetaPoolCodec, getMetaPoolCodecConfig(1)]
codecs: [UniswapV3ExactInputCodec, CurvePoolCodec, getSpecialMetaPoolCodecConfig(1), getMetaPoolCodecConfig(1)]
},

// BSC
Expand All @@ -43,7 +37,7 @@ export const deploymentConfigs: IConfig = {
{ address: '0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff', func: UniswapV2SwapExactTokensForTokensCodec.func }, // Quickswap: UniswapV2Router02
...getSupportedCurvePools(137)
],
codecs: [UniswapV2SwapExactTokensForTokensCodec, CurveSpecialMetaPoolCodec, getMetaPoolCodecConfig(137)]
codecs: [UniswapV2SwapExactTokensForTokensCodec, getSpecialMetaPoolCodecConfig(137), getMetaPoolCodecConfig(137)]
},

// Fantom
Expand All @@ -68,7 +62,7 @@ export const deploymentConfigs: IConfig = {
codecs: [
UniswapV2SwapExactTokensForTokensCodec,
CurvePoolCodec,
CurveSpecialMetaPoolCodec,
getSpecialMetaPoolCodecConfig(43114),
getMetaPoolCodecConfig(43114)
]
},
Expand Down
20 changes: 17 additions & 3 deletions deploy/configs/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import toml from 'toml';
import {
CurveMetaPoolCodecBase,
CurvePoolCodec,
CurveSpecialMetaPoolCodec,
CurveSpecialMetaPoolCodecBase,
ICodecConfig,
IDexConfig,
IMetaPoolArgs,
Expand Down Expand Up @@ -40,7 +40,7 @@ export const getSupportedCurvePools = (chainId: number): IDexConfig[] => {
dexConfig.func = CurveMetaPoolCodecBase.func;
break;
case 'meta-special':
dexConfig.func = CurveSpecialMetaPoolCodec.func;
dexConfig.func = CurveSpecialMetaPoolCodecBase.func;
break;
default:
throw Error(`malformed pool type (${pool.type})`);
Expand All @@ -51,7 +51,7 @@ export const getSupportedCurvePools = (chainId: number): IDexConfig[] => {

export const getMetaPoolCodecConfig = (chainId: number): ICodecConfig => {
const args = getCurvePools(chainId)
.filter((pool) => pool.type === 'meta' || pool.type === 'meta-special')
.filter((pool) => pool.type === 'meta')
.reduce<IMetaPoolArgs>(
(args, p) => {
args[0].push(p.address);
Expand All @@ -63,6 +63,20 @@ export const getMetaPoolCodecConfig = (chainId: number): ICodecConfig => {
return { ...CurveMetaPoolCodecBase, args };
};

export const getSpecialMetaPoolCodecConfig = (chainId: number): ICodecConfig => {
const args = getCurvePools(chainId)
.filter((pool) => pool.type === 'meta-special')
.reduce<IMetaPoolArgs>(
(args, p) => {
args[0].push(p.address);
args[1].push(p.tokens);
return args;
},
[[], []]
);
return { ...CurveSpecialMetaPoolCodecBase, args };
};

export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

export const verify = async (hre: HardhatRuntimeEnvironment, deployResult: DeployResult, args?: any) => {
Expand Down
2 changes: 1 addition & 1 deletion deploy/configs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const CurveMetaPoolCodecBase: ICodecConfig = {
func: 'exchange_underlying(int128,int128,uint256,uint256,address)'
};

export const CurveSpecialMetaPoolCodec: ICodecConfig = {
export const CurveSpecialMetaPoolCodecBase: ICodecConfig = {
name: 'CurveSpecialMetaPoolCodec',
func: 'exchange_underlying(int128,int128,uint256,uint256)'
};
6 changes: 4 additions & 2 deletions test/lib/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ export async function deployChainhopContracts(
signer: string,
feeCollector: string,
messageBus: string,
supportedDexList: string[]
supportedDexList: string[],
supportedDexFuncs: string[]
): Promise<ChainHopContracts> {
const v2CodecFactory = (await ethers.getContractFactory(
'UniswapV2SwapExactTokensForTokensCodec'
Expand Down Expand Up @@ -118,7 +119,8 @@ export async function deployChainhopContracts(
'exactInput((bytes,address,uint256,uint256,uint256))'
],
[v2Codec.address, curveCodec.address, v3Codec.address],
supportedDexList
supportedDexList,
supportedDexFuncs
);
await xswap.deployed();

Expand Down
6 changes: 4 additions & 2 deletions test/lib/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ export const chainhopFixture = async ([admin]: Wallet[]): Promise<ChainhopFixtur
signer.address,
feeCollector.address,
bridge.messageBus.address,
[dex.mockV2.address, dex.mockCurve.address]
[dex.mockV2.address, dex.mockCurve.address],
['swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', 'exchange(int128,int128,uint256,uint256)']
);
await fundTokens(tokens, dex.mockCurve.address);
await fundTokens(tokens, dex.mockV2.address);
Expand All @@ -81,7 +82,8 @@ export const benchmarkFixture = async ([admin]: Wallet[]): Promise<BenchmarkFixt
signer.address,
feeCollector.address,
bridge.messageBus.address,
[dex.mockV2.address]
[dex.mockV2.address],
['swapExactTokensForTokens(uint256,uint256,address[],address,uint256)']
);
await fundTokens(tokens, dex.mockV2.address);
await tokens.weth.deposit({ value: parseUnits('20') });
Expand Down

0 comments on commit 6648d7e

Please sign in to comment.