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

CurveAmmAdapter #259

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

andris-balodis
Copy link

@andris-balodis andris-balodis commented Jul 12, 2022

There are a few things to consider about the CurveAmmAdapter.

  1. All curve pools have 2 ~ 4 component coins.
    CVXETH - 0x3A283D9c08E8b55966afb64C515f5143cf907611 (two tokens - WETH/CVX)
    TRICRYPTO - 0xc4AD29ba4B3c580e6D59105FFf484999997675Ff (three tokens - USDT/WBTC/WETH)

    There is no generalized query function to get component coin count of the pool.
    To support different number of component coins, the CurveAmmAdapter will accept coinCount in constructor.

  2. Each curve pool has token contract (which has IERC20 standard interface) and minter contract (which has addLiquidity / removeLiquidity functionality).
    Example curve pool with different token/minter contract address
    CVXETH token contract - 0x3A283D9c08E8b55966afb64C515f5143cf907611
    CVXETH minter contract - 0xb576491f1e6e5e62f1d8f26062ee822b40b0e0d4

    Example curve pool with same token/minter contract address
    MIM token contract - 0x5a6A4D54456819380173272A5E8E9B9904BdF41B
    MIM minter contract - 0x5a6A4D54456819380173272A5E8E9B9904BdF41B

    Since some has the same token/minter contract and some has different token/minter contract, the CurveAmmAdapter will accept poolToken and poolMinter contract addresses in constructor.

  3. The curve pools support removeLiquidityOneCoin functionality, but some pools use int128 for the coin index, some pools use uint256 for the coin index.
    MIM token contract - 0x5a6A4D54456819380173272A5E8E9B9904BdF41B (use int128 for the coin index)
    TRICRYPTO - 0xc4AD29ba4B3c580e6D59105FFf484999997675Ff (use uint256 for the coin index)

    To solve this problem, the CurveAmmAdapter will accept a flag that tells if the pool uses int128 or uint256 for coin index.

  4. The curve pools has addLiquidity and removeLiquidity functionality but it doesn't accept any recipient address. So we can't specify setToken address in the calldata.
    This is fixed by introducing addLiquidity, removeLiquidity and removeLiquidityOneCoin wrapper functions in CurveAmmAdapter contract.
    E.g. addLiquidity wrapper function will accept the recipient address. It will get component tokens from msg.sender, add liquidity & mint pool tokens, transfer pool tokens back to recipient address.

@andris-balodis andris-balodis marked this pull request as ready for review July 13, 2022 03:20
Copy link
Contributor

@FlattestWhite FlattestWhite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also add to the PR description with all the curve contracts that you've looked at in the process of writing this contract, and your approach + various design decisions you've made and why you've made them?

poolMinter = _poolMinter;
isCurveV1 = _isCurveV1;
coinCount = _coinCount;
for (uint256 i = 0 ; i < _coinCount ; ++i) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to execute these functions without storing coins and coinIndex?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this coins and coinIndex is to used to check if component token is valid. without storing coins and coinIndex, it will require more gas each time trying to get calldata.

contracts/protocol/integration/amm/CurveAmmAdapter.sol Outdated Show resolved Hide resolved
) public {
poolToken = _poolToken;
poolMinter = _poolMinter;
isCurveV1 = _isCurveV1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to ensure that the contract passed in is curveV1 or curveV2?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no proper way to check the interface

contracts/protocol/integration/amm/CurveAmmAdapter.sol Outdated Show resolved Hide resolved
contracts/protocol/integration/amm/CurveAmmAdapter.sol Outdated Show resolved Hide resolved
contracts/protocol/integration/amm/CurveAmmAdapter.sol Outdated Show resolved Hide resolved
@FlattestWhite
Copy link
Contributor

I've only looked closely at the contract so far, leaving a more detailed look to the tests later

Copy link
Contributor

@FlattestWhite FlattestWhite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks really good - just a few more comments on the tests and some validation checks

test/integration/curveAmmModule.spec.ts Outdated Show resolved Hide resolved
test/integration/curveAmmModule.spec.ts Outdated Show resolved Hide resolved
test/integration/curveAmmModule.spec.ts Outdated Show resolved Hide resolved
test/protocol/integration/amm/CurveAmmAdapter.spec.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@FlattestWhite FlattestWhite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm after fixing the comments on the tests

params: [
{
forking: {
jsonRpcUrl: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_TOKEN}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's mainnet - isn't hardhat already set up for forking?

}

const expectedNewLpTokens =
coinCount === 2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a switch statement here for readability

// `calc_token_amount` of `USDT/WBTC/WETH` pool return correct amount out for add_liquidity, but it doesn't return for `MIM/3CRV` pools.
// there is some external logic for fees, here we test actual output and expected output with 0.1% slippage
expectCloseTo(
(await poolToken.balanceOf(setToken.address)).sub(lpBalanceBefore),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extract this out to separate variable

let ammModule: AmmModule;

before(async () => {
await network.provider.request({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is unit test, shouldn't need these hardhat fork setup

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants