-
Notifications
You must be signed in to change notification settings - Fork 2
/
Controller.sol
207 lines (172 loc) · 8.27 KB
/
Controller.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.11;
import "../../deps/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "../../deps/@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol";
import "../../deps/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import "../../deps/@openzeppelin/contracts-upgradeable/token/ERC20/SafeERC20Upgradeable.sol";
import "../../deps/@openzeppelin/contracts-upgradeable/proxy/Initializable.sol";
import "interfaces/badger/IConverter.sol";
import "interfaces/badger/IOneSplitAudit.sol";
import "interfaces/badger/IStrategy.sol";
import "../../deps/SettAccessControl.sol";
contract Controller is SettAccessControl {
using SafeERC20Upgradeable for IERC20Upgradeable;
using AddressUpgradeable for address;
using SafeMathUpgradeable for uint256;
address public onesplit;
address public rewards;
mapping(address => address) public vaults;
mapping(address => address) public strategies;
mapping(address => mapping(address => address)) public converters;
mapping(address => mapping(address => bool)) public approvedStrategies;
uint256 public split = 500;
uint256 public constant max = 10000;
/// @param _governance Can take any permissioned action within the Controller, with the exception of Vault helper functions
/// @param _strategist Can configure new Vaults, choose strategies among those approved by governance, and call operations involving non-core tokens
/// @param _keeper An extra address that can call earn() to deposit tokens from a Sett into the associated active Strategy. Designed for use by a trusted bot in leiu of having this function publically callable.
/// @param _rewards The recipient of standard fees (such as performance and withdrawal fees) from Strategies
function initialize(
address _governance,
address _strategist,
address _keeper,
address _rewards
) public initializer {
governance = _governance;
strategist = _strategist;
keeper = _keeper;
rewards = _rewards;
onesplit = address(0x50FDA034C0Ce7a8f7EFDAebDA7Aa7cA21CC1267e);
}
// ===== Modifiers =====
/// @notice The Sett for a given token or any of the permissioned roles can call earn() to deposit accumulated deposit funds from the Sett to the active Strategy
function _onlyApprovedForWant(address want) internal view {
require(
msg.sender == vaults[want] ||
msg.sender == keeper ||
msg.sender == strategist ||
msg.sender == governance,
"!authorized"
);
}
// ===== View Functions =====
/// @notice Get the balance of the given tokens' current strategy of that token.
function balanceOf(address _token) external view returns (uint256) {
return IStrategy(strategies[_token]).balanceOf();
}
function getExpectedReturn(
address _strategy,
address _token,
uint256 parts
) public view returns (uint256 expected) {
uint256 _balance = IERC20Upgradeable(_token).balanceOf(_strategy);
address _want = IStrategy(_strategy).want();
(expected, ) = IOneSplitAudit(onesplit).getExpectedReturn(
_token,
_want,
_balance,
parts,
0
);
}
// ===== Permissioned Actions: Governance Only =====
/// @notice Approve the given address as a Strategy for a want. The Strategist can freely switch between approved stratgies for a token.
function approveStrategy(address _token, address _strategy) public {
_onlyGovernance();
approvedStrategies[_token][_strategy] = true;
}
/// @notice Revoke approval for the given address as a Strategy for a want.
function revokeStrategy(address _token, address _strategy) public {
_onlyGovernance();
approvedStrategies[_token][_strategy] = false;
}
/// @notice Change the recipient of rewards for standard fees from Strategies
function setRewards(address _rewards) public {
_onlyGovernance();
rewards = _rewards;
}
function setSplit(uint256 _split) public {
_onlyGovernance();
split = _split;
}
/// @notice Change the oneSplit contract, which is used in conversion of non-core strategy rewards
function setOneSplit(address _onesplit) public {
_onlyGovernance();
onesplit = _onesplit;
}
// ===== Permissioned Actions: Governance or Strategist =====
/// @notice Set the Vault (aka Sett) for a given want
/// @notice The vault can only be set once
function setVault(address _token, address _vault) public {
_onlyGovernanceOrStrategist();
require(vaults[_token] == address(0), "vault");
vaults[_token] = _vault;
}
/// @notice Migrate assets from existing strategy to a new strategy.
/// @notice The new strategy must have been previously approved by governance.
/// @notice Strategist or governance can freely switch between approved strategies
function setStrategy(address _token, address _strategy) public {
_onlyGovernanceOrStrategist();
require(approvedStrategies[_token][_strategy] == true, "!approved");
address _current = strategies[_token];
if (_current != address(0)) {
IStrategy(_current).withdrawAll();
}
strategies[_token] = _strategy;
}
/// @notice Set the contract used to convert between two given assets
function setConverter(
address _input,
address _output,
address _converter
) public {
_onlyGovernanceOrStrategist();
converters[_input][_output] = _converter;
}
/// @notice Withdraw the entire balance of a token from that tokens' current strategy.
/// @notice Does not trigger a withdrawal fee.
/// @notice Entire balance will be sent to corresponding Sett.
function withdrawAll(address _token) public {
_onlyGovernanceOrStrategist();
IStrategy(strategies[_token]).withdrawAll();
}
/// @dev Transfer an amount of the specified token from the controller to the sender.
/// @dev Token balance are never meant to exist in the controller, this is purely a safeguard.
function inCaseTokensGetStuck(address _token, uint256 _amount) public {
_onlyGovernanceOrStrategist();
IERC20Upgradeable(_token).safeTransfer(msg.sender, _amount);
}
/// @dev Transfer an amount of the specified token from the controller to the sender.
/// @dev Token balance are never meant to exist in the controller, this is purely a safeguard.
function inCaseStrategyTokenGetStuck(address _strategy, address _token)
public
{
_onlyGovernanceOrStrategist();
IStrategy(_strategy).withdrawOther(_token);
}
// ==== Permissioned Actions: Only Approved Actors =====
/// @notice Deposit given token to strategy, converting it to the strategies' want first (if required).
/// @dev Only the associated vault, or permissioned actors can call this function (keeper, strategist, governance)
/// @param _token Token to deposit (will be converted to want by converter). If no converter is registered, the transaction will revert.
/// @param _amount Amount of token to deposit
function earn(address _token, uint256 _amount) public {
address _strategy = strategies[_token];
address _want = IStrategy(_strategy).want();
_onlyApprovedForWant(_want);
if (_want != _token) {
address converter = converters[_token][_want];
IERC20Upgradeable(_token).safeTransfer(converter, _amount);
_amount = IConverter(converter).convert(_strategy);
IERC20Upgradeable(_want).safeTransfer(_strategy, _amount);
} else {
IERC20Upgradeable(_token).safeTransfer(_strategy, _amount);
}
IStrategy(_strategy).deposit();
}
// ===== Permissioned Actions: Only Associated Vault =====
/// @notice Wtihdraw a given token from it's corresponding strategy
/// @notice Only the associated vault can call, in response to a user withdrawal request
function withdraw(address _token, uint256 _amount) public {
require(msg.sender == vaults[_token], "!vault");
IStrategy(strategies[_token]).withdraw(_amount);
}
}