Skip to content

Commit 19d09e9

Browse files
feat: FlashMintPerp
* Add ExchangeIssuancePerp.sol * Use double quotes * Add deploy/test scripts for ExchangeIssuancePerp * add comments & fix unit test coverage * add optimism integration test for MNYe * rename ExchangeIssuancePerp to FlashMintPerp Co-authored-by: crypto-ticky <[email protected]>
1 parent fa0bff6 commit 19d09e9

File tree

17 files changed

+3063
-102
lines changed

17 files changed

+3063
-102
lines changed

.circleci/config.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,29 @@ jobs:
103103
name: Hardhat Test
104104
command: yarn test:integration:polygon
105105

106+
test_integration_optimism:
107+
docker:
108+
- image: cimg/node:lts
109+
working_directory: ~/index-coop-smart-contracts
110+
steps:
111+
- setup_remote_docker:
112+
docker_layer_caching: false
113+
- run:
114+
name: Fetch solc version
115+
command: docker pull ethereum/solc:0.6.10
116+
- restore_cache:
117+
key: compiled-env-{{ .Environment.CIRCLE_SHA1 }}
118+
- run:
119+
name: Set Up Environment Variables
120+
command: cp .env.default .env
121+
- run:
122+
name: Test RPC
123+
command: yarn chain:fork:optimism
124+
background: true
125+
- run:
126+
name: Hardhat Test
127+
command: yarn test:integration:optimism
128+
106129
test_integration_ethereum:
107130
docker:
108131
- image: cimg/node:lts
@@ -231,6 +254,9 @@ workflows:
231254
- test_integration_polygon:
232255
requires:
233256
- checkout_and_compile
257+
- test_integration_optimism:
258+
requires:
259+
- checkout_and_compile
234260
- coverage:
235261
requires:
236262
- checkout_and_compile
Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
/*
2+
Copyright 2022 Index Cooperative
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+
http://www.apache.org/licenses/LICENSE-2.0
8+
Unless required by applicable law or agreed to in writing, software
9+
distributed under the License is distributed on an "AS IS" BASIS,
10+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
See the License for the specific language governing permissions and
12+
limitations under the License.
13+
14+
SPDX-License-Identifier: Apache License, Version 2.0
15+
*/
16+
17+
pragma solidity 0.6.10;
18+
pragma experimental ABIEncoderV2;
19+
20+
import { IQuoter } from "../interfaces/IQuoter.sol";
21+
import { IUniswapV3SwapCallback } from "../interfaces/IUniswapV3SwapCallback.sol";
22+
import { ISwapRouter} from "../interfaces/external/ISwapRouter.sol";
23+
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
24+
import { Context } from "@openzeppelin/contracts/GSN/Context.sol";
25+
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
26+
import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol";
27+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
28+
import { TransferHelper } from "../lib/TransferHelper.sol";
29+
import { ISetToken } from "../interfaces/ISetToken.sol";
30+
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
31+
import { ISlippageIssuanceModule } from "../interfaces/ISlippageIssuanceModule.sol";
32+
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
33+
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
34+
import { Withdrawable } from "external/contracts/aaveV2/utils/Withdrawable.sol";
35+
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
36+
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
37+
38+
39+
/**
40+
* @title FlashMintPerp
41+
*
42+
* Flash issue basis trading products using SlippageIssuanceModule
43+
*
44+
*/
45+
contract FlashMintPerp is Withdrawable {
46+
using PreciseUnitMath for uint256;
47+
using SafeMath for uint256;
48+
using SafeCast for int256;
49+
50+
51+
////////////// State //////////////
52+
53+
ISlippageIssuanceModule public immutable slippageIssuanceModule;
54+
ISwapRouter public immutable uniV3Router;
55+
IQuoter public immutable uniV3Quoter;
56+
IERC20 public immutable usdc;
57+
mapping (ISetToken => SetPoolInfo) public setPoolInfo;
58+
mapping(ISetToken => bool) public initializedSets;
59+
60+
////////////// Structs //////////////
61+
62+
struct SetPoolInfo {
63+
bytes spotToUsdcRoute;
64+
address spotToken;
65+
}
66+
67+
////////////// Constructor //////////////
68+
69+
constructor(
70+
ISwapRouter _uniV3Router,
71+
IQuoter _uniV3Quoter,
72+
ISlippageIssuanceModule _slippageIssuanceModule,
73+
IERC20 _usdc
74+
)
75+
public
76+
{
77+
uniV3Router = _uniV3Router;
78+
uniV3Quoter = _uniV3Quoter;
79+
slippageIssuanceModule = _slippageIssuanceModule;
80+
usdc = _usdc;
81+
82+
// Approve USDC to SlippageIssuanceModule
83+
_usdc.approve(address(_slippageIssuanceModule), PreciseUnitMath.maxUint256());
84+
85+
// Approve USDC
86+
_usdc.approve(address(_uniV3Router), PreciseUnitMath.maxUint256());
87+
}
88+
89+
///////////// Modifier ///////////////
90+
91+
modifier isInitializedSet(ISetToken _setToken) {
92+
require(initializedSets[_setToken], "Set not initialized");
93+
_;
94+
}
95+
96+
////////// Helper functions /////////////
97+
98+
/**
99+
* @dev Approve specific amount of token to spender
100+
*
101+
* @param _token Address of the token which needs approval
102+
* @param _spender Address of the spender which will be approved to spend token. (Must be a whitlisted issuance module)
103+
* @param _amount The amount of tokens to approve
104+
*/
105+
function approve(address _token, address _spender, uint256 _amount) external onlyOwner {
106+
TransferHelper.safeApprove(_token, _spender, _amount);
107+
}
108+
109+
/**
110+
* @dev Enable the SetToken issuance
111+
*
112+
* @param _setToken Address of the SetToken to be issued
113+
* @param _spotToUsdcRoute Uniswap V3 Path to be used for exchange
114+
* @param _spotToken Address of the spot token
115+
*/
116+
function initializeSet(
117+
ISetToken _setToken,
118+
bytes calldata _spotToUsdcRoute,
119+
address _spotToken
120+
)
121+
external
122+
onlyOwner
123+
{
124+
// Approve spot token to V3 and SIM
125+
TransferHelper.safeApprove(_spotToken, address(uniV3Router), PreciseUnitMath.maxUint256());
126+
TransferHelper.safeApprove(_spotToken, address(slippageIssuanceModule), PreciseUnitMath.maxUint256());
127+
128+
// Store SetToken pool data in mapping
129+
setPoolInfo[_setToken] = SetPoolInfo({
130+
spotToUsdcRoute: _spotToUsdcRoute,
131+
spotToken: _spotToken
132+
});
133+
134+
initializedSets[_setToken] = true;
135+
}
136+
137+
/**
138+
* @dev Disable the SetToken issuance
139+
*
140+
* @param _setToken Address of the SetToken to be issued
141+
*/
142+
function removeSet(ISetToken _setToken) external onlyOwner {
143+
delete setPoolInfo[_setToken];
144+
initializedSets[_setToken] = false;
145+
}
146+
147+
///////////////// Getter Functions /////////////////////
148+
149+
/**
150+
* Returns USDC amount required for issuance
151+
*
152+
* @param _setToken Address of the SetToken
153+
* @param _amountOut The issuance amount of the SetToken
154+
*/
155+
function getUsdcAmountInForFixedSetOffChain(
156+
ISetToken _setToken,
157+
uint256 _amountOut
158+
)
159+
external
160+
returns (uint256 totalUsdcAmountIn)
161+
{
162+
// Get units and components
163+
(
164+
address[] memory slippageIssuanceComponents,
165+
uint256[] memory slippageIssuanceUnits,
166+
) = slippageIssuanceModule.getRequiredComponentIssuanceUnitsOffChain(
167+
_setToken,
168+
_amountOut
169+
);
170+
171+
// Assert assumptions
172+
require(slippageIssuanceComponents.length <= 2, "invalid set");
173+
174+
// calculate total usdc amount in and usdcForSpot
175+
for (uint256 i = 0; i < slippageIssuanceComponents.length; i++) {
176+
177+
if (slippageIssuanceComponents[i] == address(usdc)) {
178+
179+
totalUsdcAmountIn = totalUsdcAmountIn.add(slippageIssuanceUnits[i]);
180+
181+
} else {
182+
183+
uint256 usdcForSpot = uniV3Quoter.quoteExactOutput(
184+
setPoolInfo[_setToken].spotToUsdcRoute,
185+
slippageIssuanceUnits[i].add(1) // Add 1 wei
186+
);
187+
totalUsdcAmountIn = totalUsdcAmountIn.add(usdcForSpot);
188+
}
189+
}
190+
}
191+
192+
/**
193+
* Returns USDC amount required for redemption
194+
*
195+
* @param _setToken Address of the SetToken
196+
* @param _amountIn The redeem amount of the SetToken
197+
*/
198+
function getUsdcAmountOutForFixedSetOffChain(
199+
ISetToken _setToken,
200+
uint256 _amountIn
201+
)
202+
external
203+
returns (uint256 totalUsdcAmountOut)
204+
{
205+
// Get underlying spot and usdc units
206+
(
207+
address[] memory slippageIssuanceComponents,
208+
uint256[] memory slippageIssuanceUnits,
209+
) = slippageIssuanceModule.getRequiredComponentRedemptionUnitsOffChain(
210+
_setToken,
211+
_amountIn
212+
);
213+
214+
// Assert assumptions
215+
require(slippageIssuanceComponents.length <= 2, "invalid set");
216+
217+
// calculate total usdc amount in and usdcFromSpot
218+
for (uint256 i = 0; i < slippageIssuanceComponents.length; i++) {
219+
if (slippageIssuanceComponents[i] == address(usdc)) {
220+
221+
totalUsdcAmountOut = totalUsdcAmountOut.add(slippageIssuanceUnits[i]);
222+
223+
} else {
224+
225+
uint256 usdcFromSpot = uniV3Quoter.quoteExactInput(
226+
setPoolInfo[_setToken].spotToUsdcRoute,
227+
slippageIssuanceUnits[i].sub(1) // Leave 1 wei
228+
);
229+
totalUsdcAmountOut = totalUsdcAmountOut.add(usdcFromSpot);
230+
}
231+
}
232+
}
233+
234+
//////////////// External Functions ////////////////////
235+
236+
/**
237+
* Issue expected amount of SetToken using USDC
238+
*
239+
* @param _setToken Address of the SetToken
240+
* @param _amount The expected issuance amount of the SetToken
241+
* @param _maxAmountIn The maximum input amount of USDC
242+
*/
243+
function issueFixedSetFromUsdc(
244+
ISetToken _setToken,
245+
uint256 _amount,
246+
uint256 _maxAmountIn
247+
)
248+
external
249+
isInitializedSet(_setToken)
250+
{
251+
// Transfer max amount in
252+
TransferHelper.safeTransferFrom(address(usdc), msg.sender, address(this), _maxAmountIn);
253+
254+
// calculate spot asset quantity
255+
uint256 spotAssetQuantity = _spotAssetQuantity(_setToken, _amount);
256+
257+
// Trade USDC for exact spot token
258+
ISwapRouter.ExactOutputParams memory spotTokenParams = ISwapRouter.ExactOutputParams(
259+
setPoolInfo[_setToken].spotToUsdcRoute,
260+
address(this),
261+
block.timestamp,
262+
spotAssetQuantity.add(1), // Add 1 wei
263+
PreciseUnitMath.maxUint256() // No need for slippage check
264+
);
265+
266+
// Executes the swap
267+
uniV3Router.exactOutput(spotTokenParams);
268+
269+
// Issue Set with spot tokens and USDC
270+
slippageIssuanceModule.issueWithSlippage(
271+
_setToken,
272+
_amount,
273+
new address[](0), // No need to check for slippage cause L2; If not enough USDC then issue would fail
274+
new uint256[](0),
275+
msg.sender
276+
);
277+
278+
// Return unused USDC
279+
uint256 usdcBalance = usdc.balanceOf(address(this));
280+
TransferHelper.safeTransfer(address(usdc), msg.sender, usdcBalance);
281+
}
282+
283+
/**
284+
* Redeem expected amount of SetToken using USDC
285+
*
286+
* @param _setToken Address of the SetToken
287+
* @param _amount The expected redeem amount of the SetToken
288+
* @param _minAmountOut The minimum output amount of USDC
289+
*/
290+
function redeemFixedSetForUsdc(
291+
ISetToken _setToken,
292+
uint256 _amount,
293+
uint256 _minAmountOut
294+
)
295+
external
296+
isInitializedSet(_setToken)
297+
{
298+
299+
TransferHelper.safeTransferFrom(address(_setToken), msg.sender, address(this), _amount);
300+
301+
// Redeem Set to spot tokens and USDC
302+
slippageIssuanceModule.redeemWithSlippage(
303+
_setToken,
304+
_amount,
305+
new address[](0), // No need to check for slippage as there is no risk of sandwiching due to flashloans
306+
new uint256[](0),
307+
address(this)
308+
);
309+
310+
// calculate spot asset quantity
311+
uint256 spotAssetQuantity = _spotAssetQuantity(_setToken, _amount);
312+
313+
// check with actual spot token balance
314+
uint256 spotTokenBalance = IERC20(setPoolInfo[_setToken].spotToken).balanceOf(address(this));
315+
if (spotAssetQuantity > spotTokenBalance) {
316+
spotAssetQuantity = spotTokenBalance;
317+
}
318+
319+
ISwapRouter.ExactInputParams memory spotTokenParams = ISwapRouter.ExactInputParams(
320+
setPoolInfo[_setToken].spotToUsdcRoute,
321+
address(this),
322+
block.timestamp,
323+
spotAssetQuantity.sub(1), // Leave 1 wei
324+
0 // No need for slippage check
325+
);
326+
327+
// Executes the swap
328+
uniV3Router.exactInput(spotTokenParams);
329+
330+
// Return the USDC
331+
uint256 usdcBalance = usdc.balanceOf(address(this));
332+
333+
require(usdcBalance >= _minAmountOut, "Not enough USDC");
334+
335+
TransferHelper.safeTransfer(address(usdc), msg.sender, usdcBalance);
336+
}
337+
338+
339+
/////////////// Internal functions //////////////////
340+
341+
function _spotAssetQuantity(ISetToken _setToken, uint256 _amount) internal view returns (uint256) {
342+
address spotAsset = setPoolInfo[_setToken].spotToken;
343+
344+
uint256 spotAssetQuantity = _setToken
345+
.getDefaultPositionRealUnit(spotAsset)
346+
.toUint256()
347+
.preciseMul(_amount);
348+
349+
return spotAssetQuantity;
350+
}
351+
}

0 commit comments

Comments
 (0)