Skip to content

Commit 777ed94

Browse files
fix(adapter): Support Curve stableswap pools (#253)
1 parent 7ef234b commit 777ed94

18 files changed

+1394
-142
lines changed

contracts/interfaces/external/IStableSwapStEth.sol renamed to contracts/interfaces/external/IStableSwapPool.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
pragma solidity 0.6.10;
1616

1717
/**
18-
* Curve StableSwap pool for stETH.
18+
* Curve StableSwap ERC20 <-> ERC20 pool.
1919
*/
20-
interface IStableSwapStEth {
20+
interface IStableSwapPool {
21+
2122
function exchange(
2223
int128 i,
2324
int128 j,

contracts/mocks/external/CurveStEthStableswapMock.sol renamed to contracts/mocks/external/CurveStableswapMock.sol

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.s
2424
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
2525
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
2626

27-
// Minimal Curve Eth/StEth Stableswap Pool
28-
contract CurveStEthStableswapMock is ReentrancyGuard {
27+
// Minimal Curve Stableswap Pool
28+
contract CurveStableswapMock is ReentrancyGuard {
29+
30+
address public constant ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
2931

3032
using SafeERC20 for IERC20;
3133
using SafeMath for uint256;
@@ -35,13 +37,20 @@ contract CurveStEthStableswapMock is ReentrancyGuard {
3537
address[] tokens;
3638

3739
constructor(address[] memory _tokens) public {
38-
require(_tokens[1] != address(0));
40+
for (uint i = 0; i < _tokens.length; i++) {
41+
require(_tokens[i] != address(0));
42+
}
3943
tokens = _tokens;
4044
}
4145

4246
function add_liquidity(uint256[] memory _amounts, uint256 _min_mint_amount) payable external nonReentrant returns (uint256) {
43-
require(_amounts[0] == msg.value, "Eth sent should equal amount");
44-
IERC20(tokens[1]).safeTransferFrom(msg.sender, address(this), _amounts[1]);
47+
for (uint i = 0; i < _amounts.length; i++) {
48+
if (tokens[i] == ETH_TOKEN_ADDRESS) {
49+
require(_amounts[i] == msg.value, "Eth sent should equal amount");
50+
continue;
51+
}
52+
IERC20(tokens[i]).safeTransferFrom(msg.sender, address(this), _amounts[i]);
53+
}
4554
return _min_mint_amount;
4655
}
4756

@@ -56,18 +65,19 @@ contract CurveStEthStableswapMock is ReentrancyGuard {
5665
function exchange(int128 _i, int128 _j, uint256 _dx, uint256 _min_dy) payable external nonReentrant returns (uint256) {
5766
require(_i != _j);
5867
require(_dx == _min_dy);
59-
if (_i == 0 && _j == 1) {
60-
// The caller has sent eth receive stETH
61-
require(_dx == msg.value);
62-
IERC20(tokens[1]).safeTransfer(msg.sender, _dx);
63-
} else if (_j == 0 && _i == 1) {
64-
// The caller has sent stETH to receive ETH
65-
IERC20(tokens[1]).safeTransferFrom(msg.sender, address(this), _dx);
66-
Address.sendValue(msg.sender, _dx);
68+
69+
if (tokens[uint256(_i)] == ETH_TOKEN_ADDRESS) {
70+
require(_dx == msg.value);
71+
} else {
72+
IERC20(tokens[uint256(_i)]).transferFrom(msg.sender, address(this), _dx);
73+
}
74+
75+
if (tokens[uint256(_j)] == ETH_TOKEN_ADDRESS) {
76+
Address.sendValue(payable(msg.sender), _min_dy);
6777
} else {
68-
revert("Invalid index values");
78+
IERC20(tokens[uint256(_j)]).transfer(msg.sender, _min_dy);
6979
}
70-
return _dx;
80+
return _min_dy;
7181
}
7282

7383
/**
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/*
2+
Copyright 2022 Set Labs Inc.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
SPDX-License-Identifier: Apache License, Version 2.0
13+
*/
14+
15+
pragma solidity 0.6.10;
16+
pragma experimental "ABIEncoderV2";
17+
18+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
19+
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
20+
21+
import { IStableSwapPool } from "../../../interfaces/external/IStableSwapPool.sol";
22+
import { IWETH } from "../../../interfaces/external/IWETH.sol";
23+
import { PreciseUnitMath } from "../../../lib/PreciseUnitMath.sol";
24+
25+
/**
26+
* @title CurveExchangeAdapter
27+
* @author FlattestWhite
28+
*
29+
* Exchange adapter for Curve pools for ERC20 <-> ERC20
30+
* exchange contracts. This contract assumes that all tokens
31+
* being traded are ERC20 tokens.
32+
*
33+
* This contract is intended to be used by trade modules to rebalance
34+
* SetTokens that hold ERC20 tokens as part of its components.
35+
*/
36+
contract CurveExchangeAdapter {
37+
38+
using SafeMath for uint256;
39+
using PreciseUnitMath for uint256;
40+
41+
/* ========= State Variables ========= */
42+
43+
// Address of ERC20 tokenA
44+
IERC20 immutable public tokenA;
45+
// Address of ERC20 tokenB
46+
IERC20 immutable public tokenB;
47+
// Index of tokenA
48+
int128 immutable public tokenAIndex;
49+
// Index of tokenB
50+
int128 immutable public tokenBIndex;
51+
// Address of Curve tokenA/tokenB stableswap pool.
52+
IStableSwapPool immutable public stableswap;
53+
54+
/* ========= Constructor ========== */
55+
56+
/**
57+
* Set state variables
58+
*
59+
* @param _tokenA Address of tokenA
60+
* @param _tokenB Address of tokenB
61+
* @param _tokenAIndex Index of tokenA in stableswap pool
62+
* @param _tokenBIndex Index of tokenB in stableswap pool
63+
* @param _stableswap Address of Curve Stableswap pool
64+
*/
65+
constructor(
66+
IERC20 _tokenA,
67+
IERC20 _tokenB,
68+
int128 _tokenAIndex,
69+
int128 _tokenBIndex,
70+
IStableSwapPool _stableswap
71+
)
72+
public
73+
{
74+
require(_stableswap.coins(uint256(_tokenAIndex)) == address(_tokenA), "Stableswap pool has invalid index for tokenA");
75+
require(_stableswap.coins(uint256(_tokenBIndex)) == address(_tokenB), "Stableswap pool has invalid index for tokenB");
76+
77+
tokenA = _tokenA;
78+
tokenB = _tokenB;
79+
tokenAIndex = _tokenAIndex;
80+
tokenBIndex = _tokenBIndex;
81+
stableswap = _stableswap;
82+
83+
_tokenA.approve(address(_stableswap), PreciseUnitMath.maxUint256());
84+
_tokenB.approve(address(_stableswap), PreciseUnitMath.maxUint256());
85+
}
86+
87+
/* ============ External Getter Functions ============ */
88+
89+
/**
90+
* Calculate Curve trade encoded calldata. To be invoked on the SetToken.
91+
*
92+
* @param _sourceToken The input token.
93+
* @param _destinationToken The output token.
94+
* @param _destinationAddress The address where the proceeds of the output is sent to.
95+
* @param _sourceQuantity Amount of input token.
96+
* @param _minDestinationQuantity The minimum amount of output token to be received.
97+
*
98+
* @return address Target contract address
99+
* @return uint256 Call value
100+
* @return bytes Trade calldata
101+
*/
102+
function getTradeCalldata(
103+
address _sourceToken,
104+
address _destinationToken,
105+
address _destinationAddress,
106+
uint256 _sourceQuantity,
107+
uint256 _minDestinationQuantity,
108+
bytes memory /* data */
109+
)
110+
external
111+
view
112+
returns (address, uint256, bytes memory)
113+
{
114+
require(_sourceToken != _destinationToken, "_sourceToken must not be the same as _destinationToken");
115+
require(_sourceToken == address(tokenA) || _sourceToken == address(tokenB), "Invalid sourceToken");
116+
require(_destinationToken == address(tokenA) || _destinationToken == address(tokenB), "Invalid destinationToken");
117+
118+
bytes memory callData = abi.encodeWithSignature("trade(address,address,uint256,uint256,address)",
119+
_sourceToken,
120+
_destinationToken,
121+
_sourceQuantity,
122+
_minDestinationQuantity,
123+
_destinationAddress
124+
);
125+
return (address(this), 0, callData);
126+
}
127+
128+
/* ============ External Functions ============ */
129+
130+
/**
131+
* Invokes an exchange on Curve Stableswap pool. To be invoked on the SetToken.
132+
*
133+
* @param _sourceToken The input token.
134+
* @param _destinationToken The output token.
135+
* @param _sourceQuantity Amount of input token.
136+
* @param _minDestinationQuantity The minimum amount of output token to be received.
137+
* @param _destinationAddress The address where the proceeds of the output is sent to.
138+
*/
139+
function trade(
140+
address _sourceToken,
141+
address _destinationToken,
142+
uint256 _sourceQuantity,
143+
uint256 _minDestinationQuantity,
144+
address _destinationAddress
145+
) external {
146+
require(_sourceToken != _destinationToken, "_sourceToken must not be the same as _destinationToken");
147+
if (_sourceToken == address(tokenA) && _destinationToken == address(tokenB)) {
148+
// Transfers sourceToken
149+
IERC20(_sourceToken).transferFrom(msg.sender, address(this), _sourceQuantity);
150+
151+
// Exchange sourceToken for destinationToken
152+
uint256 amountOut = stableswap.exchange(tokenAIndex, tokenBIndex, _sourceQuantity, _minDestinationQuantity);
153+
154+
// Transfer destinationToken to destinationAddress
155+
IERC20(_destinationToken).transfer(_destinationAddress, amountOut);
156+
} else if (_sourceToken == address(tokenB) && _destinationToken == address(tokenA)) {
157+
// Transfers sourceToken
158+
IERC20(_sourceToken).transferFrom(msg.sender, address(this), _sourceQuantity);
159+
160+
// Exchange sourceToken for destinationToken
161+
uint256 amountOut = stableswap.exchange(tokenBIndex, tokenAIndex, _sourceQuantity, _minDestinationQuantity) ;
162+
163+
// Transfer destinationToken to destinationAddress
164+
IERC20(_destinationToken).transfer(_destinationAddress, amountOut);
165+
} else {
166+
revert("Invalid _sourceToken or _destinationToken or both");
167+
}
168+
}
169+
170+
/**
171+
* Returns the address to approve source tokens to for trading. In this case, the address of this contract.
172+
*
173+
* @return address Address of the contract to approve tokens to.
174+
*/
175+
function getSpender() external view returns (address) {
176+
return address(this);
177+
}
178+
}

contracts/protocol/integration/exchange/CurveStEthExchangeAdapter.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pragma experimental "ABIEncoderV2";
1818
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
1919
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
2020

21-
import { IStableSwapStEth } from "../../../interfaces/external/IStableSwapStEth.sol";
21+
import { IStableSwapPool } from "../../../interfaces/external/IStableSwapPool.sol";
2222
import { IWETH } from "../../../interfaces/external/IWETH.sol";
2323
import { PreciseUnitMath } from "../../../lib/PreciseUnitMath.sol";
2424

@@ -46,7 +46,7 @@ contract CurveStEthExchangeAdapter {
4646
// Address of stETH token.
4747
IERC20 immutable public stETH;
4848
// Address of Curve Eth/StEth stableswap pool.
49-
IStableSwapStEth immutable public stableswap;
49+
IStableSwapPool immutable public stableswap;
5050
// Index for ETH for Curve stableswap pool.
5151
int128 internal constant ETH_INDEX = 0;
5252
// Index for stETH for Curve stableswap pool.
@@ -64,7 +64,7 @@ contract CurveStEthExchangeAdapter {
6464
constructor(
6565
IWETH _weth,
6666
IERC20 _stETH,
67-
IStableSwapStEth _stableswap
67+
IStableSwapPool _stableswap
6868
)
6969
public
7070
{

hardhat.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
require("dotenv").config();
2-
require('hardhat-contract-sizer');
2+
require("hardhat-contract-sizer");
33

44
import chalk from "chalk";
55
import { HardhatUserConfig } from "hardhat/config";
@@ -12,7 +12,7 @@ import "./tasks";
1212

1313
const forkingConfig = {
1414
url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_TOKEN}`,
15-
blockNumber: 12198000,
15+
blockNumber: 14792479,
1616
};
1717

1818
const mochaConfig = {

0 commit comments

Comments
 (0)