Skip to content

Commit f697783

Browse files
feat(adapter): Add Curve trade adapter [SIM-173] (#238)
1 parent ff8102a commit f697783

File tree

14 files changed

+1223
-8
lines changed

14 files changed

+1223
-8
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
17+
/**
18+
* Curve StableSwap pool for stETH.
19+
*/
20+
interface IStableSwapStEth {
21+
function exchange(
22+
int128 i,
23+
int128 j,
24+
uint256 dx,
25+
uint256 min_dy
26+
) external payable returns (uint256);
27+
28+
function coins(uint256) external view returns (address);
29+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
Copyright 2022 Set Labs Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache License, Version 2.0
17+
*/
18+
19+
pragma solidity 0.6.10;
20+
21+
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
22+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
23+
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
24+
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
25+
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
26+
27+
// Minimal Curve Eth/StEth Stableswap Pool
28+
contract CurveStEthStableswapMock is ReentrancyGuard {
29+
30+
using SafeERC20 for IERC20;
31+
using SafeMath for uint256;
32+
using SafeMath for int128;
33+
using Address for address;
34+
35+
address[] tokens;
36+
37+
constructor(address[] memory _tokens) public {
38+
require(_tokens[1] != address(0));
39+
tokens = _tokens;
40+
}
41+
42+
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]);
45+
return _min_mint_amount;
46+
}
47+
48+
/**
49+
* @dev Index values can be found via the `coins` public getter method
50+
* @param _i Index value for the coin to send
51+
* @param _j Index value of the coin to receive
52+
* @param _dx Amount of `i` being exchanged
53+
* @param _min_dy Minimum amount of `j` to receive
54+
* @return Actual amount of `j` received
55+
*/
56+
function exchange(int128 _i, int128 _j, uint256 _dx, uint256 _min_dy) payable external nonReentrant returns (uint256) {
57+
require(_i != _j);
58+
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);
67+
} else {
68+
revert("Invalid index values");
69+
}
70+
return _dx;
71+
}
72+
73+
/**
74+
* @param _index Index to look up address for.
75+
*
76+
* @return address Address of the token at index
77+
*/
78+
function coins(uint256 _index) external view returns (address) {
79+
return tokens[_index];
80+
}
81+
}
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
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 { IStableSwapStEth } from "../../../interfaces/external/IStableSwapStEth.sol";
22+
import { IWETH } from "../../../interfaces/external/IWETH.sol";
23+
import { PreciseUnitMath } from "../../../lib/PreciseUnitMath.sol";
24+
25+
/**
26+
* @title CurveStEthExchangeAdapter
27+
* @author FlattestWhite & ncitron
28+
*
29+
* Exchange adapter for the specialized Curve stETH <-> ETH
30+
* exchange contracts. Implements helper functionality for
31+
* wrapping and unwrapping WETH since the curve exchange uses
32+
* raw ETH.
33+
*
34+
* This contract is intended to be used by trade modules to rebalance
35+
* SetTokens that hold stETH as part of its components.
36+
*/
37+
contract CurveStEthExchangeAdapter {
38+
39+
using SafeMath for uint256;
40+
using PreciseUnitMath for uint256;
41+
42+
/* ========= State Variables ========= */
43+
44+
// Address of WETH token.
45+
IWETH immutable public weth;
46+
// Address of stETH token.
47+
IERC20 immutable public stETH;
48+
// Address of Curve Eth/StEth stableswap pool.
49+
IStableSwapStEth immutable public stableswap;
50+
// Index for ETH for Curve stableswap pool.
51+
int128 internal constant ETH_INDEX = 0;
52+
// Index for stETH for Curve stableswap pool.
53+
int128 internal constant STETH_INDEX = 1;
54+
55+
/* ========= Constructor ========== */
56+
57+
/**
58+
* Set state variables
59+
*
60+
* @param _weth Address of WETH token
61+
* @param _stETH Address of stETH token
62+
* @param _stableswap Address of Curve Eth/StEth Stableswap pool
63+
*/
64+
constructor(
65+
IWETH _weth,
66+
IERC20 _stETH,
67+
IStableSwapStEth _stableswap
68+
)
69+
public
70+
{
71+
weth = _weth;
72+
stETH = _stETH;
73+
stableswap = _stableswap;
74+
75+
require(_stableswap.coins(0) == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, "Stableswap pool has invalid ETH_INDEX");
76+
require(_stableswap.coins(1) == address(_stETH), "Stableswap pool has invalid STETH_INDEX");
77+
78+
_stETH.approve(address(_stableswap), PreciseUnitMath.maxUint256());
79+
}
80+
81+
/* ======== External Functions ======== */
82+
83+
/**
84+
* Buys stEth using WETH
85+
*
86+
* @param _sourceQuantity The amount of WETH as input.
87+
* @param _minDestinationQuantity The minimum amounnt of stETH to receive.
88+
* @param _destinationAddress The address to send the trade proceeds to.
89+
*/
90+
function buyStEth(
91+
uint256 _sourceQuantity,
92+
uint256 _minDestinationQuantity,
93+
address _destinationAddress
94+
)
95+
external
96+
{
97+
// transfer weth
98+
weth.transferFrom(msg.sender, address(this), _sourceQuantity);
99+
100+
// unwrap weth
101+
weth.withdraw(_sourceQuantity);
102+
103+
// buy stETH
104+
uint256 amountOut = stableswap.exchange{value: _sourceQuantity} (
105+
ETH_INDEX,
106+
STETH_INDEX,
107+
_sourceQuantity,
108+
_minDestinationQuantity
109+
);
110+
111+
// transfer proceeds
112+
stETH.transfer(_destinationAddress, amountOut);
113+
}
114+
115+
/**
116+
* Sells stETH for WETH
117+
*
118+
* @param _sourceQuantity The amount of stETH as input.
119+
* @param _minDestinationQuantity The minimum amount of WETH to receive.
120+
* @param _destinationAddress The address to send the trade proceeds to.
121+
*/
122+
function sellStEth(
123+
uint256 _sourceQuantity,
124+
uint256 _minDestinationQuantity,
125+
address _destinationAddress
126+
)
127+
external
128+
{
129+
// transfer stETH
130+
stETH.transferFrom(msg.sender, address(this), _sourceQuantity);
131+
132+
// sell stETH
133+
uint256 amountOut = stableswap.exchange(STETH_INDEX, ETH_INDEX, _sourceQuantity, _minDestinationQuantity);
134+
135+
// wrap eth
136+
weth.deposit{value: amountOut}();
137+
138+
// transfer proceeds
139+
weth.transfer(_destinationAddress, amountOut);
140+
}
141+
142+
/* ============ External Getter Functions ============ */
143+
144+
/**
145+
* Calculate Curve trade encoded calldata. To be invoked on the SetToken.
146+
*
147+
* @param _sourceToken Either WETH or stETH. The input token.
148+
* @param _destinationToken Either WETH or stETH. The output token.
149+
* @param _destinationAddress The address where the proceeds of the output is sent to.
150+
* @param _sourceQuantity Amount of input token.
151+
* @param _minDestinationQuantity The minimum amount of output token to be received.
152+
*
153+
* @return address Target contract address
154+
* @return uint256 Call value
155+
* @return bytes Trade calldata
156+
*/
157+
function getTradeCalldata(
158+
address _sourceToken,
159+
address _destinationToken,
160+
address _destinationAddress,
161+
uint256 _sourceQuantity,
162+
uint256 _minDestinationQuantity,
163+
bytes memory /* data */
164+
)
165+
external
166+
view
167+
returns (address, uint256, bytes memory)
168+
{
169+
if (_sourceToken == address(weth) && _destinationToken == address(stETH)) {
170+
bytes memory callData = abi.encodeWithSignature(
171+
"buyStEth(uint256,uint256,address)",
172+
_sourceQuantity,
173+
_minDestinationQuantity,
174+
_destinationAddress
175+
);
176+
return (address(this), 0, callData);
177+
} else if (_sourceToken == address(stETH) && _destinationToken == address(weth)) {
178+
bytes memory callData = abi.encodeWithSignature(
179+
"sellStEth(uint256,uint256,address)",
180+
_sourceQuantity,
181+
_minDestinationQuantity,
182+
_destinationAddress
183+
);
184+
return (address(this), 0, callData);
185+
} else {
186+
revert("Must swap between weth and stETH");
187+
}
188+
}
189+
190+
/**
191+
* Returns the address to approve source tokens to for trading. In this case, the address of this contract.
192+
*
193+
* @return address Address of the contract to approve tokens to.
194+
*/
195+
function getSpender() external view returns (address) {
196+
return address(this);
197+
}
198+
199+
/**
200+
* This function is invoked when:
201+
* 1. WETH is withdrawn for ETH.
202+
* 2. ETH is received from Curve stableswap pool on exchange call.
203+
*/
204+
receive() external payable {}
205+
}

0 commit comments

Comments
 (0)