Skip to content

Curve amm adapter #155

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

Closed
wants to merge 12 commits into from
20 changes: 20 additions & 0 deletions contracts/interfaces/external/IMetaPoolZap.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
pragma solidity 0.6.10;

interface IMetaPoolZap {
function add_liquidity(
uint256[2] calldata _depositAmounts,
uint256 _minMintAmount
) external returns (uint256);

function remove_liquidity(
address _pool,
uint256 _burnAmount,
uint256[2] calldata _minAmounts
) external;

function remove_liquidity_one_coin(
uint256 _burnAmount,
int128 _i,
uint256 _minAmount
) external;
}
22 changes: 22 additions & 0 deletions contracts/interfaces/external/IMetapoolFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
Copyright 2021 Set Labs Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

SPDX-License-Identifier: Apache License, Version 2.0
*/

pragma solidity 0.6.10;

interface IMetapoolFactory {
//Will return [exoticStable, 3CRV]
function get_coins(address _pool) external view returns (address[2] memory);
}
41 changes: 41 additions & 0 deletions contracts/interfaces/external/ITriPoolZap.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
Copyright 2021 Set Labs Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

SPDX-License-Identifier: Apache License, Version 2.0
*/

pragma solidity 0.6.10;

interface ITriPoolZap {
function add_liquidity(
address _pool,
uint256[4] calldata _depositAmounts,
uint256 _minMintAmount,
address _receiver
) external returns (uint256);

function remove_liquidity(
address _pool,
uint256 _burnAmount,
uint256[4] calldata _minAmounts,
address _receiver
) external;

function remove_liquidity_one_coin(
address _pool,
uint256 _burnAmount,
int128 _i,
uint256 _minAmount,
address _receiver
) external;
}
176 changes: 176 additions & 0 deletions contracts/protocol/integration/amm/CurveFactoryMetapoolAmmAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
Copyright 2021 Set Labs Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

SPDX-License-Identifier: Apache License, Version 2.0
*/

pragma solidity 0.6.10;

import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";

import { IAmmAdapter } from "../../../interfaces/IAmmAdapter.sol";
import { IMetapoolFactory } from "../../../interfaces/external/IMetapoolFactory.sol";
import { IMetaPoolZap } from "../../../interfaces/external/IMetaPoolZap.sol";

contract CurveFactoryMetapoolAmmAdapter is IAmmAdapter {
Copy link
Contributor

Choose a reason for hiding this comment

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

If we can add some of the javadocs and section headers you see here for consistency.

using SafeCast for uint256;
using SafeCast for int256;

IMetapoolFactory public metapoolFactory;

string public constant ADD_LIQUIDITY = "add_liquidity(uint256[2],uint256,address)";
Copy link
Contributor

Choose a reason for hiding this comment

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

You probably want to use the ITriPoolZap functions rather than the IMetaPoolZap ones. This function requires depositing a combination of the 3Pool token and the extra token. The ITriPoolZap interface lets you deposit any of the 3Pool's pools' underlying tokens and the extra token. Makes it a lot more usable.

Copy link

@anthonymartin anthonymartin Oct 21, 2021

Choose a reason for hiding this comment

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

@ncitron it turns out that this interface & adapter is specifically for the factory metapool and not the legacy metapools. Do you know if the TriPoolZap supports the factory metapools? we had issues with the TriPoolZap reverting without any reason string response, so we ended up switching focus to the factory metapool with the intention to revisit the Zap and legacy metapool implementation afterwards.

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe they do. I had checked that it works on a whole list of pools. I think one of the gotchas on some of the different pools though is that the LP token address is different than the pool address though. I believe there is a contract for converting from LP token address to pool address and vice versa somewhere in the docs. I had previously confirmed that the zap contract works with the LUSD, alUSD, and USDN pools though (and IIRC alUSD is one of the legacy ones)

string public constant REMOVE_LIQUIDITY = "remove_liquidity(uint256,uint256[2],address)";
string public constant REMOVE_LIQUIDITY_SINGLE = "remove_liquidity_one_coin(uint256,int128,uint256,address)";

constructor(IMetapoolFactory _metapoolFactory) public {
metapoolFactory = _metapoolFactory;
}

function getProvideLiquidityCalldata(
address _setToken,
address _pool,
address[] calldata _components,
uint256[] calldata _maxTokensIn,
uint256 _minLiquidity
)
external
view
override
returns (address, uint256, bytes memory)
{
require(_maxTokensIn[0] > 0 && _maxTokensIn[1] > 0, "tokens in must be nonzero");
uint256[2] memory inputAmounts = _convertUintArrayLiteral(_maxTokensIn);

bytes memory callData = abi.encodeWithSignature(
ADD_LIQUIDITY,
inputAmounts,
_minLiquidity,
_setToken
);

return (_pool, 0, callData);
}

function getProvideLiquiditySingleAssetCalldata(
address _setToken,
address _pool,
address _component,
uint256 _maxTokenIn,
uint256 _minLiquidity
)
external
view
override
returns (address, uint256, bytes memory)
{
require(_maxTokenIn > 0, "tokens in must be nonzero");
uint256 tokenIndex = _getTokenIndex(_pool, _component);

uint256[2] memory inputAmounts;
inputAmounts[tokenIndex] = _maxTokenIn;

bytes memory callData = abi.encodeWithSignature(
ADD_LIQUIDITY,
inputAmounts,
_minLiquidity,
_setToken
);

return (_pool, 0, callData);
}

function getRemoveLiquidityCalldata(
address _setToken,
address _pool,
address[] calldata _components,
uint256[] calldata _minTokensOut,
uint256 _liquidity
)
external
view
override
returns (address, uint256, bytes memory)
{
uint256[2] memory outputAmounts = _convertUintArrayLiteral(_minTokensOut);

bytes memory callData = abi.encodeWithSignature(
REMOVE_LIQUIDITY,
_liquidity,
outputAmounts,
_setToken
);

return (_pool, 0, callData);
}

function getRemoveLiquiditySingleAssetCalldata(
address _setToken,
address _pool,
address _component,
uint256 _minTokenOut,
uint256 _liquidity
)
external
view
override
returns (address, uint256, bytes memory)
{
int128 i = _getTokenIndex(_pool, _component).toInt256().toInt128();

bytes memory callData = abi.encodeWithSignature(
REMOVE_LIQUIDITY_SINGLE,
_liquidity,
i,
_minTokenOut,
_setToken
);

return (_pool, 0, callData);
}

function getSpenderAddress(address _pool) external view override returns(address) {
return _pool;
}

function isValidPool(address _pool, address[] memory _components) external view override returns(bool) {
return _isValidPool(_pool, _components);
}

function _isValidPool(address _pool, address[] memory _components) internal view returns(bool) {
address[2] memory expectedTokens = metapoolFactory.get_coins(_pool);

for (uint256 i = 0; i < _components.length; i++) {
if (!(_components[i] == expectedTokens[0] || _components[i] == expectedTokens[1])) {
return false;
}
}

return true;
}

function _convertUintArrayLiteral(uint256[] memory _arr) internal pure returns (uint256[2] memory _literal) {
for (uint256 i = 0; i < 2; i++) {
_literal[i] = _arr[i];
}
return _literal;
}

function _getTokenIndex(address _pool, address _token) internal view returns (uint256) {
address[2] memory underlying = metapoolFactory.get_coins(_pool);
for (uint256 i = 0; i < 2; i++) {
if (underlying[i] == _token) return i;
}
}
}
2 changes: 1 addition & 1 deletion contracts/protocol/modules/AmmModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ contract AmmModule is ModuleBase, ReentrancyGuard {
actionInfo.preActionComponentBalances = _getTokenBalances(address(_setToken), _components);

actionInfo.liquidityQuantity = actionInfo.totalSupply.getDefaultTotalNotional(_poolTokenInPositionUnit);

actionInfo.totalNotionalComponents = _getTotalNotionalComponents(_setToken, _componentUnits);

actionInfo.componentUnits = _componentUnits;
Expand Down
9 changes: 9 additions & 0 deletions curveFactoryMetapoolAmmAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## Curve FactoryMetapool Amm Adapter
Copy link
Contributor

Choose a reason for hiding this comment

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

This is great context, lets put it in the javadocs in the adapter contract and remove this markdown file. We like to keep all the context in the contract itself.

This PR contains a new amm adapter for curve and test for it.

### What for?
The CurveFactoryMetapoolAmmAdapter allows user to add or remove liquidity in curve metapools that were created with the metapoolFactory (0x0959158b6040D32d04c301A72CBFD6b39E21c9AE). An example for a pool like this is MIM-3CRV (0x5a6A4D54456819380173272A5E8E9B9904BdF41B). <b>Its important to notice that only the exotic stable coin (MIM) and 3CRV can be deposited or withdrawn.</b> Using dai/usdc/usdt would need some extra zap interaction.

### Why only these pool?
Curve has in large two types of metapools. Metapools that were created before the metapoolFactory and pools afterwards. They are quite different in terms of interfaces. Before the factory for example LP-Token were standalone ERC-20 contracts that didnt share the address with their pools. This alone validates some assumptions of the AmmModule. Additionally they do not allow to remove or add liquidity for a recipient.
It might be possible to also create an adapter for older modules but its atleast far more complicated.
7 changes: 7 additions & 0 deletions external/abi/curve/CurveAddressProvider.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"contractName":"CurveAddressProvider",
"abi":[{"name":"NewAddressIdentifier","inputs":[{"type":"uint256","name":"id","indexed":true},{"type":"address","name":"addr","indexed":false},{"type":"string","name":"description","indexed":false}],"anonymous":false,"type":"event"},{"name":"AddressModified","inputs":[{"type":"uint256","name":"id","indexed":true},{"type":"address","name":"new_address","indexed":false},{"type":"uint256","name":"version","indexed":false}],"anonymous":false,"type":"event"},{"name":"CommitNewAdmin","inputs":[{"type":"uint256","name":"deadline","indexed":true},{"type":"address","name":"admin","indexed":true}],"anonymous":false,"type":"event"},{"name":"NewAdmin","inputs":[{"type":"address","name":"admin","indexed":true}],"anonymous":false,"type":"event"},{"outputs":[],"inputs":[{"type":"address","name":"_admin"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"get_registry","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":"1061"},{"name":"max_id","outputs":[{"type":"uint256","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":"1258"},{"name":"get_address","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"_id"}],"stateMutability":"view","type":"function","gas":"1308"},{"name":"add_new_id","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_address"},{"type":"string","name":"_description"}],"stateMutability":"nonpayable","type":"function","gas":"291275"},{"name":"set_address","outputs":[{"type":"bool","name":""}],"inputs":[{"type":"uint256","name":"_id"},{"type":"address","name":"_address"}],"stateMutability":"nonpayable","type":"function","gas":"182430"},{"name":"unset_address","outputs":[{"type":"bool","name":""}],"inputs":[{"type":"uint256","name":"_id"}],"stateMutability":"nonpayable","type":"function","gas":"101348"},{"name":"commit_transfer_ownership","outputs":[{"type":"bool","name":""}],"inputs":[{"type":"address","name":"_new_admin"}],"stateMutability":"nonpayable","type":"function","gas":"74048"},{"name":"apply_transfer_ownership","outputs":[{"type":"bool","name":""}],"inputs":[],"stateMutability":"nonpayable","type":"function","gas":"60125"},{"name":"revert_transfer_ownership","outputs":[{"type":"bool","name":""}],"inputs":[],"stateMutability":"nonpayable","type":"function","gas":"21400"},{"name":"admin","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":"1331"},{"name":"transfer_ownership_deadline","outputs":[{"type":"uint256","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":"1361"},{"name":"future_admin","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":"1391"},{"name":"get_id_info","outputs":[{"type":"address","name":"addr"},{"type":"bool","name":"is_active"},{"type":"uint256","name":"version"},{"type":"uint256","name":"last_modified"},{"type":"string","name":"description"}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":"12168"}],"bytecode":"602061089561014039602061089560c03960c05160a01c1561002057600080fd5b610140516001556001600455600d610160527f4d61696e20526567697374727900000000000000000000000000000000000000610180526101608060046005600060e05260c052604060c02060c052602060c0200160c052602060c020602082510161012060006002818352015b826101205160200211156100a1576100c3565b61012051602002850151610120518501555b815160010180835281141561008e575b50505050505061087d56341561000a57600080fd5b6004361015610018576107a9565b600035601c5263a262904b600051141561003a5760005460005260206000f350005b630c6d784f600051141561006c5760045460018082101561005a57600080fd5b8082039050905060005260206000f350005b63493f4f74600051141561009e57600560043560e05260c052604060c02060c052602060c0205460005260206000f350005b63168f957960005114156102735760043560a01c156100bc57600080fd5b60606024356004016101403760406024356004013511156100dc57600080fd5b60015433146100ea57600080fd5b60006004353b116100fa57600080fd5b6004546101c05260056101c05160e05260c052604060c02060c052602060c02060043581556001600182015560016002820155426003820155610140806004830160c052602060c020602082510161012060006003818352015b8261012051602002111561016757610189565b61012051602002850151610120518501555b8151600101808352811415610154575b505050505050506101c05160018181830110156101a557600080fd5b808201905090506004556004356102205260406101e0526101e051610240526101408051602001806101e051610220018284600060045af16101e657600080fd5b50506101e05161022001518060206101e051610220010101818260206001820306601f820103905003368237505060206101e051610220015160206001820306601f82010390506101e05101016101e0526101c0517f5b0f9b31dc08c19adcc0181c1b97ad54a84487faf0a4fdcb88c8681724298af96101e051610220a26101c05160005260206000f350005b636a84cad060005114156103c45760243560a01c1561029157600080fd5b600154331461029f57600080fd5b60006024353b116102af57600080fd5b600435600454116102bf57600080fd5b6002600560043560e05260c052604060c02060c052602060c020015460018181830110156102ec57600080fd5b8082019050905061014052602435600560043560e05260c052604060c02060c052602060c0205560016001600560043560e05260c052604060c02060c052602060c0200155610140516002600560043560e05260c052604060c02060c052602060c0200155426003600560043560e05260c052604060c02060c052602060c0200155600435151561037e576024356000555b6024356101605261014051610180526004357fe7a6334c4f573efdf292d404d59adacec345f4f7c76495a034008edda0acef476040610160a2600160005260206000f350005b635eec0daa60005114156104c75760015433146103e057600080fd5b6001600560043560e05260c052604060c02060c052602060c020015461040557600080fd5b60006001600560043560e05260c052604060c02060c052602060c02001556000600560043560e05260c052604060c02060c052602060c02055426003600560043560e05260c052604060c02060c052602060c0200155600435151561046a5760006000555b6000610140526002600560043560e05260c052604060c02060c052602060c0200154610160526004357fe7a6334c4f573efdf292d404d59adacec345f4f7c76495a034008edda0acef476040610140a2600160005260206000f350005b636b441a4060005114156105665760043560a01c156104e557600080fd5b60015433146104f357600080fd5b6002541561050057600080fd5b426203f48081818301101561051457600080fd5b808201905090506101405261014051600255600435600355600435610140517f181aa3aa17d4cbf99265dd4443eba009433d3cde79d60164fde1d1a192beb93560006000a3600160005260206000f350005b636a1c05ae60005114156105ea57600154331461058257600080fd5b60006002541861059157600080fd5b6002544210156105a057600080fd5b60035461014052610140516001556000600255610140517f71614071b88dee5e0b2ae578a9dd7b2ebbe9ae832ba419dc0242cd065a290b6c60006000a2600160005260206000f350005b6386fbf193600051141561061857600154331461060657600080fd5b6000600255600160005260206000f350005b63f851a44060005114156106345760015460005260206000f350005b63e0a0b58660005114156106505760025460005260206000f350005b6317f7182a600051141561066c5760035460005260206000f350005b6392668ecb60005114156107a857600560043560e05260c052604060c0206101408060a081808560c052602060c0205481525050602082019150818060018660c052602060c020015481525050602082019150818060028660c052602060c020015481525050602082019150818060038660c052602060c0200154815250506020820191508082528083018060048660c052602060c020018060c052602060c02082602082540161012060006003818352015b8261012051602002111561073257610754565b61012051850154610120516020028501525b815160010180835281141561071f575b5050505050508051806020830101818260206001820306601f8201039050033682375050805160200160206001820306601f820103905090509050810190508090509050905060c05260c051610140f39050005b5b60006000fd5b6100ce61087d036100ce6000396100ce61087d036000f30000000000000000000000007eeac6cddbd1d0b8af061742d41877d7f707289a",
"deployedBytecode":"",
"linkReferences":{},
"deployedLinkReferences":{}
}
7 changes: 7 additions & 0 deletions external/abi/curve/CurveRegistry.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions external/abi/curve/MetapoolFactory.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"contractName":"MetapoolFactory", "abi":[{"name":"get_coins","outputs":[{"type":"address[2]","name":""}],"inputs":[{"type":"address","name":"_pool"}],"stateMutability":"view","type":"function","gas":"2427"}]}
Loading