/
PirexCvxConvex.sol
241 lines (200 loc) · 7.31 KB
/
PirexCvxConvex.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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// SPDX-License-Identifier: MIT
pragma solidity 0.8.12;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";
import {ERC20} from "@rari-capital/solmate/src/tokens/ERC20.sol";
import {SafeTransferLib} from "@rari-capital/solmate/src/utils/SafeTransferLib.sol";
import {ICvxLocker} from "./interfaces/ICvxLocker.sol";
import {ICvxDelegateRegistry} from "./interfaces/ICvxDelegateRegistry.sol";
contract PirexCvxConvex is Ownable, Pausable {
using SafeTransferLib for ERC20;
/**
@notice Convex reward details
@param token address Token
@param amount uint256 Amount
@param balance uint256 Balance (used for calculating the actual received amount)
*/
struct ConvexReward {
address token;
uint256 amount;
uint256 balance;
}
// Configurable contracts
enum ConvexContract {
CvxLocker,
CvxDelegateRegistry
}
ERC20 public immutable CVX;
ICvxLocker public cvxLocker;
ICvxDelegateRegistry public cvxDelegateRegistry;
// Convex Snapshot space
bytes32 public delegationSpace = bytes32("cvx.eth");
// The amount of CVX that needs to remain unlocked for redemptions
uint256 public outstandingRedemptions;
uint256 public pendingLocks;
event SetConvexContract(ConvexContract c, address contractAddress);
event SetDelegationSpace(string _delegationSpace);
event SetVoteDelegate(address voteDelegate);
event ClearVoteDelegate();
error ZeroAddress();
error EmptyString();
/**
@param _CVX address CVX address
@param _cvxLocker address CvxLocker address
@param _cvxDelegateRegistry address CvxDelegateRegistry address
*/
constructor(
address _CVX,
address _cvxLocker,
address _cvxDelegateRegistry
) {
if (_CVX == address(0)) revert ZeroAddress();
CVX = ERC20(_CVX);
if (_cvxLocker == address(0)) revert ZeroAddress();
cvxLocker = ICvxLocker(_cvxLocker);
if (_cvxDelegateRegistry == address(0)) revert ZeroAddress();
cvxDelegateRegistry = ICvxDelegateRegistry(_cvxDelegateRegistry);
// Max allowance for cvxLocker
CVX.safeApprove(address(cvxLocker), type(uint256).max);
}
/**
@notice Only for emergency purposes when we need to resume/halt all user actions
@param state bool Pause state
*/
function setPauseState(bool state) external onlyOwner {
if (state) {
_pause();
} else {
_unpause();
}
}
/**
@notice Set a contract address
@param c enum ConvexContract enum
@param contractAddress address Contract address
*/
function setConvexContract(ConvexContract c, address contractAddress)
external
onlyOwner
{
if (contractAddress == address(0)) revert ZeroAddress();
emit SetConvexContract(c, contractAddress);
if (c == ConvexContract.CvxLocker) {
// Revoke approval from the old locker and add allowances to the new locker
CVX.safeApprove(address(cvxLocker), 0);
cvxLocker = ICvxLocker(contractAddress);
CVX.safeApprove(contractAddress, type(uint256).max);
return;
}
cvxDelegateRegistry = ICvxDelegateRegistry(contractAddress);
}
/**
@notice Unlock CVX
*/
function _unlock() internal {
(, uint256 unlockable, , ) = cvxLocker.lockedBalances(address(this));
if (unlockable != 0) cvxLocker.processExpiredLocks(false);
}
/**
@notice Unlock CVX and relock excess
*/
function _lock() internal {
_unlock();
uint256 balance = CVX.balanceOf(address(this));
bool balanceGreaterThanRedemptions = balance > outstandingRedemptions;
// Lock CVX if the balance is greater than outstanding redemptions or if there are pending locks
if (balanceGreaterThanRedemptions || pendingLocks != 0) {
uint256 balanceRedemptionsDifference = balanceGreaterThanRedemptions
? balance - outstandingRedemptions
: 0;
// Lock amount is the greater of the two: balanceRedemptionsDifference or pendingLocks
// balanceRedemptionsDifference is greater if there is unlocked CVX that isn't reserved for redemptions + deposits
// pendingLocks is greater if there are more new deposits than unlocked CVX that is reserved for redemptions
cvxLocker.lock(
address(this),
balanceRedemptionsDifference > pendingLocks
? balanceRedemptionsDifference
: pendingLocks,
0
);
pendingLocks = 0;
}
}
/**
@notice Non-permissioned relock method
*/
function lock() external whenNotPaused {
_lock();
}
/**
@notice Get claimable rewards and balances
@return rewards ConvexReward[] Claimable rewards and balances
*/
function _claimableRewards()
internal
view
returns (ConvexReward[] memory rewards)
{
address addr = address(this);
// Get claimable rewards
ICvxLocker.EarnedData[] memory c = cvxLocker.claimableRewards(addr);
uint256 cLen = c.length;
rewards = new ConvexReward[](cLen);
// Get the current balances for each token to calculate the amount received
for (uint256 i; i < cLen; ++i) {
rewards[i] = ConvexReward({
token: c[i].token,
amount: c[i].amount,
balance: ERC20(c[i].token).balanceOf(addr)
});
}
}
/**
@notice Claim Convex rewards
*/
function _getReward() internal {
// Claim rewards from Convex
cvxLocker.getReward(address(this), false);
}
/**
@notice Set delegationSpace
@param _delegationSpace string Convex Snapshot delegation space
*/
function setDelegationSpace(string memory _delegationSpace)
external
onlyOwner
{
bytes memory d = bytes(_delegationSpace);
if (d.length == 0) revert EmptyString();
delegationSpace = bytes32(d);
emit SetDelegationSpace(_delegationSpace);
}
/**
@notice Set vote delegate
@param voteDelegate address Account to delegate votes to
*/
function setVoteDelegate(address voteDelegate) external onlyOwner {
if (voteDelegate == address(0)) revert ZeroAddress();
emit SetVoteDelegate(voteDelegate);
cvxDelegateRegistry.setDelegate(delegationSpace, voteDelegate);
}
/**
@notice Remove vote delegate
*/
function clearVoteDelegate() external onlyOwner {
emit ClearVoteDelegate();
cvxDelegateRegistry.clearDelegate(delegationSpace);
}
/**
@notice Only for emergency purposes in the case of a forced-unlock by Convex
*/
function unlock() external whenPaused onlyOwner {
_unlock();
}
/**
@notice Only for emergency purposes in the case of a forced-unlock by Convex
*/
function pausedRelock() external whenPaused onlyOwner {
_lock();
}
}