forked from curvefi/curve-contract
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLiquidityGaugeV2Mock.vy
More file actions
279 lines (229 loc) · 9.55 KB
/
LiquidityGaugeV2Mock.vy
File metadata and controls
279 lines (229 loc) · 9.55 KB
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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# @version 0.2.8
"""
@title Liquidity Gauge v2
@author Curve Finance
@license MIT
"""
from vyper.interfaces import ERC20
event Deposit:
provider: indexed(address)
value: uint256
event Withdraw:
provider: indexed(address)
value: uint256
MAX_REWARDS: constant(uint256) = 8
lp_token: public(address)
balanceOf: public(HashMap[address, uint256])
totalSupply: public(uint256)
# For tracking external rewards
reward_contract: public(address)
reward_tokens: public(address[MAX_REWARDS])
# deposit / withdraw / claim
reward_sigs: bytes32
# reward token -> integral
reward_integral: public(HashMap[address, uint256])
# reward token -> claiming address -> integral
reward_integral_for: public(HashMap[address, HashMap[address, uint256]])
@external
def __init__(lp_token: address):
self.lp_token = lp_token
@internal
def _checkpoint_rewards(_addr: address, _total_supply: uint256):
"""
@notice Claim pending rewards and checkpoint rewards for a user
"""
if _total_supply == 0:
return
balances: uint256[MAX_REWARDS] = empty(uint256[MAX_REWARDS])
reward_tokens: address[MAX_REWARDS] = empty(address[MAX_REWARDS])
for i in range(MAX_REWARDS):
token: address = self.reward_tokens[i]
if token == ZERO_ADDRESS:
break
reward_tokens[i] = token
balances[i] = ERC20(token).balanceOf(self)
# claim from reward contract
raw_call(self.reward_contract, slice(self.reward_sigs, 8, 4)) # dev: bad claim sig
for i in range(MAX_REWARDS):
token: address = reward_tokens[i]
if token == ZERO_ADDRESS:
break
dI: uint256 = 10**18 * (ERC20(token).balanceOf(self) - balances[i]) / _total_supply
if _addr == ZERO_ADDRESS:
if dI != 0:
self.reward_integral[token] += dI
continue
integral: uint256 = self.reward_integral[token] + dI
if dI != 0:
self.reward_integral[token] = integral
integral_for: uint256 = self.reward_integral_for[token][_addr]
if integral_for < integral:
claimable: uint256 = self.balanceOf[_addr] * (integral - integral_for) / 10**18
self.reward_integral_for[token][_addr] = integral
assert ERC20(token).transfer(_addr, claimable)
@external
@nonreentrant('lock')
def claimable_reward(_addr: address, _token: address) -> uint256:
"""
@notice Get the number of claimable reward tokens for a user
@dev This function should be manually changed to "view" in the ABI
Calling it via a transaction will claim available reward tokens
@param _addr Account to get reward amount for
@param _token Token to get reward amount for
@return uint256 Claimable reward token amount
"""
claimable: uint256 = ERC20(_token).balanceOf(_addr)
if self.reward_contract != ZERO_ADDRESS:
self._checkpoint_rewards(_addr, self.totalSupply)
claimable = ERC20(_token).balanceOf(_addr) - claimable
integral: uint256 = self.reward_integral[_token]
integral_for: uint256 = self.reward_integral_for[_token][_addr]
if integral_for < integral:
claimable += self.balanceOf[_addr] * (integral - integral_for) / 10**18
return claimable
@external
@nonreentrant('lock')
def claim_rewards(_addr: address = msg.sender):
"""
@notice Claim available reward tokens for `_addr`
@param _addr Address to claim for
"""
self._checkpoint_rewards(_addr, self.totalSupply)
@external
@nonreentrant('lock')
def claim_historic_rewards(_reward_tokens: address[MAX_REWARDS], _addr: address = msg.sender):
"""
@notice Claim reward tokens available from a previously-set staking contract
@param _reward_tokens Array of reward token addresses to claim
@param _addr Address to claim for
"""
for token in _reward_tokens:
if token == ZERO_ADDRESS:
break
integral: uint256 = self.reward_integral[token]
integral_for: uint256 = self.reward_integral_for[token][_addr]
if integral_for < integral:
claimable: uint256 = self.balanceOf[_addr] * (integral - integral_for) / 10**18
self.reward_integral_for[token][_addr] = integral
assert ERC20(token).transfer(_addr, claimable)
@external
@nonreentrant('lock')
def deposit(_value: uint256, _addr: address = msg.sender):
"""
@notice Deposit `_value` LP tokens
@dev Depositting also claims pending reward tokens
@param _value Number of tokens to deposit
@param _addr Address to deposit for
"""
if _value != 0:
reward_contract: address = self.reward_contract
total_supply: uint256 = self.totalSupply
if reward_contract != ZERO_ADDRESS:
self._checkpoint_rewards(_addr, total_supply)
total_supply += _value
new_balance: uint256 = self.balanceOf[_addr] + _value
self.balanceOf[_addr] = new_balance
self.totalSupply = total_supply
ERC20(self.lp_token).transferFrom(msg.sender, self, _value)
if reward_contract != ZERO_ADDRESS:
deposit_sig: Bytes[4] = slice(self.reward_sigs, 0, 4)
if convert(deposit_sig, uint256) != 0:
raw_call(
reward_contract,
concat(deposit_sig, convert(_value, bytes32))
)
log Deposit(_addr, _value)
@external
@nonreentrant('lock')
def withdraw(_value: uint256):
"""
@notice Withdraw `_value` LP tokens
@dev Withdrawing also claims pending reward tokens
@param _value Number of tokens to withdraw
"""
if _value != 0:
reward_contract: address = self.reward_contract
total_supply: uint256 = self.totalSupply
if reward_contract != ZERO_ADDRESS:
self._checkpoint_rewards(msg.sender, total_supply)
total_supply -= _value
new_balance: uint256 = self.balanceOf[msg.sender] - _value
self.balanceOf[msg.sender] = new_balance
self.totalSupply = total_supply
if reward_contract != ZERO_ADDRESS:
withdraw_sig: Bytes[4] = slice(self.reward_sigs, 4, 4)
if convert(withdraw_sig, uint256) != 0:
raw_call(
reward_contract,
concat(withdraw_sig, convert(_value, bytes32))
)
ERC20(self.lp_token).transfer(msg.sender, _value)
log Withdraw(msg.sender, _value)
@external
@nonreentrant('lock')
def set_rewards(_reward_contract: address, _sigs: bytes32, _reward_tokens: address[MAX_REWARDS]):
"""
@notice Set the active reward contract
@dev A reward contract cannot be set while this contract has no deposits
@param _reward_contract Reward contract address. Set to ZERO_ADDRESS to
disable staking.
@param _sigs Four byte selectors for staking, withdrawing and claiming,
right padded with zero bytes. If the reward contract can
be claimed from but does not require staking, the staking
and withdraw selectors should be set to 0x00
@param _reward_tokens List of claimable tokens for this reward contract
"""
lp_token: address = self.lp_token
current_reward_contract: address = self.reward_contract
total_supply: uint256 = self.totalSupply
if current_reward_contract != ZERO_ADDRESS:
self._checkpoint_rewards(ZERO_ADDRESS, total_supply)
withdraw_sig: Bytes[4] = slice(self.reward_sigs, 4, 4)
if convert(withdraw_sig, uint256) != 0:
if total_supply != 0:
raw_call(
current_reward_contract,
concat(withdraw_sig, convert(total_supply, bytes32))
)
ERC20(lp_token).approve(current_reward_contract, 0)
if _reward_contract != ZERO_ADDRESS:
assert _reward_contract.is_contract # dev: not a contract
sigs: bytes32 = _sigs
deposit_sig: Bytes[4] = slice(sigs, 0, 4)
withdraw_sig: Bytes[4] = slice(sigs, 4, 4)
if convert(deposit_sig, uint256) != 0:
# need a non-zero total supply to verify the sigs
assert total_supply != 0 # dev: zero total supply
ERC20(lp_token).approve(_reward_contract, MAX_UINT256)
# it would be Very Bad if we get the signatures wrong here, so
# we do a test deposit and withdrawal prior to setting them
raw_call(
_reward_contract,
concat(deposit_sig, convert(total_supply, bytes32))
) # dev: failed deposit
assert ERC20(lp_token).balanceOf(self) == 0
raw_call(
_reward_contract,
concat(withdraw_sig, convert(total_supply, bytes32))
) # dev: failed withdraw
assert ERC20(lp_token).balanceOf(self) == total_supply
# deposit and withdraw are good, time to make the actual deposit
raw_call(
_reward_contract,
concat(deposit_sig, convert(total_supply, bytes32))
)
else:
assert convert(withdraw_sig, uint256) == 0 # dev: withdraw without deposit
self.reward_contract = _reward_contract
self.reward_sigs = _sigs
for i in range(MAX_REWARDS):
if _reward_tokens[i] != ZERO_ADDRESS:
self.reward_tokens[i] = _reward_tokens[i]
elif self.reward_tokens[i] != ZERO_ADDRESS:
self.reward_tokens[i] = ZERO_ADDRESS
else:
assert i != 0 # dev: no reward token
break
if _reward_contract != ZERO_ADDRESS:
# do an initial checkpoint to verify that claims are working
self._checkpoint_rewards(ZERO_ADDRESS, total_supply)