Skip to content

Commit b4e2c4e

Browse files
authored
feat: ReinvestmentExtensionV1 (#201)
* add ReinvestmentExtensionV1
1 parent 67b7445 commit b4e2c4e

File tree

5 files changed

+939
-0
lines changed

5 files changed

+939
-0
lines changed
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/*
2+
Copyright 2024 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+
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+
pragma experimental ABIEncoderV2;
21+
22+
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
23+
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
24+
25+
import { BaseExtension } from "../lib/BaseExtension.sol";
26+
import { IBaseManager } from "../interfaces/IBaseManager.sol";
27+
import { ISetToken } from "../interfaces/ISetToken.sol";
28+
import { IAirdropModule } from "../interfaces/IAirdropModule.sol";
29+
import { ITradeModule } from "../interfaces/ITradeModule.sol";
30+
import { IWrapModuleV2 } from "../interfaces/IWrapModuleV2.sol";
31+
32+
/**
33+
* @title ReinvestmentExtensionV1
34+
* @author Index Cooperative
35+
*/
36+
contract ReinvestmentExtensionV1 is BaseExtension {
37+
38+
using Address for address;
39+
using SafeCast for int256;
40+
41+
/* ========== Structs ================= */
42+
43+
struct ExchangeSettings {
44+
string exchangeName;
45+
bytes exchangeCallData;
46+
}
47+
48+
/* ========== State Variables ========= */
49+
50+
address public immutable WETH;
51+
ISetToken public immutable setToken;
52+
IAirdropModule public immutable airdropModule;
53+
ITradeModule public immutable tradeModule;
54+
IWrapModuleV2 public immutable wrapModule;
55+
56+
mapping(address => ExchangeSettings) public exchangeSettings;
57+
mapping(address => mapping(address => bool)) public approvedWrapPairs;
58+
59+
/* ============ Constructor ============ */
60+
61+
constructor(
62+
IBaseManager _manager,
63+
address _weth,
64+
IAirdropModule _airdropModule,
65+
ITradeModule _tradeModule,
66+
IWrapModuleV2 _wrapModule,
67+
address[] memory _initialRewardTokens,
68+
ExchangeSettings[] memory _initialExchangeSettings,
69+
address[][] memory _initialWrapPairs
70+
) public BaseExtension(_manager) {
71+
require(_weth != address(0), "Invalid WETH address");
72+
73+
setToken = _manager.setToken();
74+
WETH = _weth;
75+
airdropModule = _airdropModule;
76+
tradeModule = _tradeModule;
77+
wrapModule = _wrapModule;
78+
79+
require(_initialRewardTokens.length == _initialExchangeSettings.length, "Arrays length mismatch");
80+
81+
for (uint256 i = 0; i < _initialRewardTokens.length; i++) {
82+
require(_initialRewardTokens[i] != address(0), "Invalid reward token");
83+
exchangeSettings[_initialRewardTokens[i]] = _initialExchangeSettings[i];
84+
}
85+
86+
for (uint256 i = 0; i < _initialWrapPairs.length; i++) {
87+
address underlyingToken = _initialWrapPairs[i][0];
88+
address wrappedToken = _initialWrapPairs[i][1];
89+
require(underlyingToken != address(0) && wrappedToken != address(0), "Invalid token address");
90+
approvedWrapPairs[underlyingToken][wrappedToken] = true;
91+
}
92+
}
93+
94+
/* ============ External Functions ============ */
95+
96+
/**
97+
* APPROVED_CALLER ONLY: Absorbs airdropped tokens and trades them for WETH
98+
*
99+
* @param _rewardToken Address of reward token to reinvest
100+
* @param _minReceiveQuantity Minimum amount of WETH to receive
101+
*/
102+
function reinvest(
103+
address _rewardToken,
104+
uint256 _minReceiveQuantity
105+
) external onlyAllowedCaller(msg.sender) {
106+
bytes memory absorbCallData = abi.encodeWithSelector(
107+
IAirdropModule.absorb.selector,
108+
setToken,
109+
_rewardToken
110+
);
111+
invokeManager(address(airdropModule), absorbCallData);
112+
113+
uint256 rewardUnits = uint256(setToken.getTotalComponentRealUnits(_rewardToken));
114+
require(rewardUnits > 0, "Reward units must be greater than zero");
115+
bytes memory tradeCallData = abi.encodeWithSelector(
116+
ITradeModule.trade.selector,
117+
setToken,
118+
exchangeSettings[_rewardToken].exchangeName,
119+
_rewardToken,
120+
rewardUnits,
121+
WETH,
122+
_minReceiveQuantity,
123+
exchangeSettings[_rewardToken].exchangeCallData
124+
);
125+
invokeManager(address(tradeModule), tradeCallData);
126+
}
127+
128+
/**
129+
* OPERATOR ONLY: Wraps underlying token into target wrapped token
130+
*
131+
* @param _underlyingToken Address of underlying token
132+
* @param _wrappedToken Address of wrapped token
133+
* @param _underlyingUnits Units of underlying token to wrap
134+
* @param _integrationName Name of wrap module integration
135+
* @param _wrapData Encoded wrap data
136+
*/
137+
function wrap(
138+
address _underlyingToken,
139+
address _wrappedToken,
140+
uint256 _underlyingUnits,
141+
string calldata _integrationName,
142+
bytes memory _wrapData
143+
) external onlyAllowedCaller(msg.sender) {
144+
require(_underlyingUnits > 0, "Invalid units");
145+
require(approvedWrapPairs[_underlyingToken][_wrappedToken], "Unapproved wrap pair");
146+
147+
bytes memory wrapCallData = abi.encodeWithSelector(
148+
wrapModule.wrap.selector,
149+
setToken,
150+
_underlyingToken,
151+
_wrappedToken,
152+
_underlyingUnits,
153+
_integrationName,
154+
_wrapData
155+
);
156+
invokeManager(address(wrapModule), wrapCallData);
157+
}
158+
159+
/**
160+
* OPERATOR ONLY: Adds new token to airdrop list
161+
*
162+
* @param _token Address of token to add to airdrop list
163+
*/
164+
function addAirdrop(address _token) external onlyOperator {
165+
invokeManager(
166+
address(airdropModule),
167+
abi.encodeWithSignature("addAirdrop(address,address)", setToken, _token)
168+
);
169+
}
170+
171+
/**
172+
* OPERATOR ONLY: Updates exchange settings for a reward token
173+
*/
174+
function updateExchangeSettings(
175+
address _rewardToken,
176+
ExchangeSettings memory _settings
177+
) external onlyOperator {
178+
require(_rewardToken != address(0), "Invalid reward token");
179+
exchangeSettings[_rewardToken] = _settings;
180+
}
181+
182+
/**
183+
* OPERATOR ONLY: Adds an approved wrap pair
184+
*
185+
* @param _underlyingToken Address of underlying token
186+
* @param _wrappedToken Address of wrapped token
187+
*/
188+
function addWrapPair(address _underlyingToken, address _wrappedToken) external onlyOperator {
189+
require(_underlyingToken != address(0) && _wrappedToken != address(0), "Invalid token address");
190+
require(!approvedWrapPairs[_underlyingToken][_wrappedToken], "Pair already exists");
191+
approvedWrapPairs[_underlyingToken][_wrappedToken] = true;
192+
}
193+
194+
/**
195+
* OPERATOR ONLY: Removes an approved wrap pair
196+
*
197+
* @param _underlyingToken Address of underlying token
198+
* @param _wrappedToken Address of wrapped token
199+
*/
200+
function removeWrapPair(address _underlyingToken, address _wrappedToken) external onlyOperator {
201+
require(_underlyingToken != address(0) && _wrappedToken != address(0), "Invalid token address");
202+
require(approvedWrapPairs[_underlyingToken][_wrappedToken], "Pair does not exist");
203+
delete approvedWrapPairs[_underlyingToken][_wrappedToken];
204+
}
205+
206+
/**
207+
* OPERATOR ONLY: Initializes the AirdropModule
208+
*
209+
* @param _airdropSettings Airdrop module initialization settings
210+
*/
211+
function initializeAirdropModule(IAirdropModule.AirdropSettings memory _airdropSettings) external onlyOperator {
212+
bytes memory callData = abi.encodeWithSelector(
213+
airdropModule.initialize.selector,
214+
setToken,
215+
_airdropSettings
216+
);
217+
invokeManager(address(airdropModule), callData);
218+
}
219+
220+
/**
221+
* OPERATOR ONLY: Initializes the TradeModule
222+
*/
223+
function initializeTradeModule() external onlyOperator {
224+
bytes memory tradeModuleData = abi.encodeWithSelector(tradeModule.initialize.selector, setToken);
225+
invokeManager(address(tradeModule), tradeModuleData);
226+
}
227+
228+
/**
229+
* OPERATOR ONLY: Initializes the WrapModule
230+
*/
231+
function initializeWrapModule() external onlyOperator {
232+
bytes memory wrapModuleData = abi.encodeWithSelector(wrapModule.initialize.selector, setToken);
233+
invokeManager(address(wrapModule), wrapModuleData);
234+
}
235+
}

0 commit comments

Comments
 (0)