-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathChildGaugeFactory.vy
331 lines (257 loc) · 9.66 KB
/
ChildGaugeFactory.vy
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
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# @version 0.3.7
"""
@title Child Liquidity Gauge Factory
@license MIT
@author Curve Finance
"""
version: public(constant(String[8])) = "2.0.0"
from vyper.interfaces import ERC20
interface ChildGauge:
def initialize(_lp_token: address, _root: address, _manager: address): nonpayable
def integrate_fraction(_user: address) -> uint256: view
def user_checkpoint(_user: address) -> bool: nonpayable
interface CallProxy:
def anyCall(
_to: address, _data: Bytes[1024], _fallback: address, _to_chain_id: uint256
): nonpayable
event DeployedGauge:
_implementation: indexed(address)
_lp_token: indexed(address)
_deployer: indexed(address)
_salt: bytes32
_gauge: address
event Minted:
_user: indexed(address)
_gauge: indexed(address)
_new_total: uint256
event UpdateImplementation:
_old_implementation: address
_new_implementation: address
event UpdateVotingEscrow:
_old_voting_escrow: address
_new_voting_escrow: address
event UpdateCallProxy:
_old_call_proxy: address
_new_call_proxy: address
event UpdateMirrored:
_gauge: indexed(address)
_mirrored: bool
event TransferOwnership:
_old_owner: address
_new_owner: address
WEEK: constant(uint256) = 86400 * 7
crv: public(ERC20)
get_implementation: public(address)
voting_escrow: public(address)
owner: public(address)
future_owner: public(address)
root_factory: public(address)
root_implementation: bytes20
call_proxy: public(address)
# [last_request][has_counterpart][is_valid_gauge]
gauge_data: public(HashMap[address, uint256])
# user -> gauge -> value
minted: public(HashMap[address, HashMap[address, uint256]])
get_gauge_from_lp_token: public(HashMap[address, address])
get_gauge_count: public(uint256)
get_gauge: public(address[max_value(int128)])
@external
def __init__(_call_proxy: address, _root_factory: address, _root_impl: address, _crv: address, _owner: address):
"""
@param _call_proxy Contract for
@param _root_factory Root factory to anchor to
@param _root_impl Address of root gauge implementation to calculate mirror (can be updated)
@param _crv Bridged CRV token address (might be zero if not known yet)
@param _owner Owner of factory (xgov)
"""
self.crv = ERC20(_crv)
self.call_proxy = _call_proxy
log UpdateCallProxy(empty(address), _call_proxy)
assert _root_factory != empty(address)
assert _root_impl != empty(address)
self.root_factory = _root_factory
self.root_implementation = convert(_root_impl, bytes20)
self.owner = _owner
log TransferOwnership(empty(address), _owner)
@internal
def _psuedo_mint(_gauge: address, _user: address):
gauge_data: uint256 = self.gauge_data[_gauge]
assert gauge_data != 0 # dev: invalid gauge
# if is_mirrored and last_request != this week
if bitwise_and(gauge_data, 2) != 0 and shift(gauge_data, -2) / WEEK != block.timestamp / WEEK:
CallProxy(self.call_proxy).anyCall(
self,
_abi_encode(_gauge, method_id=method_id("transmit_emissions(address)")),
empty(address),
1,
)
# update last request time
self.gauge_data[_gauge] = shift(block.timestamp, 2) + 3
assert ChildGauge(_gauge).user_checkpoint(_user)
total_mint: uint256 = ChildGauge(_gauge).integrate_fraction(_user)
to_mint: uint256 = total_mint - self.minted[_user][_gauge]
if to_mint != 0 and self.crv != empty(ERC20):
assert self.crv.transfer(_user, to_mint, default_return_value=True)
self.minted[_user][_gauge] = total_mint
log Minted(_user, _gauge, total_mint)
@external
@nonreentrant("lock")
def mint(_gauge: address):
"""
@notice Mint everything which belongs to `msg.sender` and send to them
@param _gauge `LiquidityGauge` address to get mintable amount from
"""
self._psuedo_mint(_gauge, msg.sender)
@external
@nonreentrant("lock")
def mint_many(_gauges: address[32]):
"""
@notice Mint everything which belongs to `msg.sender` across multiple gauges
@param _gauges List of `LiquidityGauge` addresses
"""
for i in range(32):
if _gauges[i] == empty(address):
pass
self._psuedo_mint(_gauges[i], msg.sender)
@external
def deploy_gauge(_lp_token: address, _salt: bytes32, _manager: address = msg.sender) -> address:
"""
@notice Deploy a liquidity gauge
@param _lp_token The token to deposit in the gauge
@param _manager The address to set as manager of the gauge
@param _salt A value to deterministically deploy a gauge
"""
if self.get_gauge_from_lp_token[_lp_token] != empty(address):
# overwriting lp_token -> gauge mapping requires
assert msg.sender == self.owner # dev: only owner
gauge_data: uint256 = 1 # set is_valid_gauge = True
implementation: address = self.get_implementation
salt: bytes32 = keccak256(_abi_encode(chain.id, msg.sender, _salt))
gauge: address = create_minimal_proxy_to(
implementation, salt=salt
)
if msg.sender == self.call_proxy:
gauge_data += 2 # set mirrored = True
log UpdateMirrored(gauge, True)
# issue a call to the root chain to deploy a root gauge
CallProxy(self.call_proxy).anyCall(
self,
_abi_encode(chain.id, _salt, method_id=method_id("deploy_gauge(uint256,bytes32)")),
empty(address),
1
)
self.gauge_data[gauge] = gauge_data
idx: uint256 = self.get_gauge_count
self.get_gauge[idx] = gauge
self.get_gauge_count = idx + 1
self.get_gauge_from_lp_token[_lp_token] = gauge
# derive root gauge address
gauge_codehash: bytes32 = keccak256(
concat(
0x602d3d8160093d39f3363d3d373d3d3d363d73,
self.root_implementation,
0x5af43d82803e903d91602b57fd5bf3,
)
)
digest: bytes32 = keccak256(concat(0xFF, convert(self.root_factory, bytes20), salt, gauge_codehash))
root: address = convert(convert(digest, uint256) & convert(max_value(uint160), uint256), address)
# If root is uninitialized, self.owner can always set the root gauge manually
# on the gauge contract itself via set_root_gauge method
ChildGauge(gauge).initialize(_lp_token, root, _manager)
log DeployedGauge(implementation, _lp_token, msg.sender, _salt, gauge)
return gauge
@external
def set_crv(_crv: ERC20):
"""
@notice Sets CRV token address
@dev Child gauges reference the factory to fetch CRV address
If empty, the gauges do not mint any CRV tokens.
@param _crv address of CRV token on child chain
"""
assert msg.sender == self.owner
assert _crv != empty(ERC20)
self.crv = _crv
@external
def set_voting_escrow(_voting_escrow: address):
"""
@notice Update the voting escrow contract
@param _voting_escrow Contract to use as the voting escrow oracle
"""
assert msg.sender == self.owner # dev: only owner
log UpdateVotingEscrow(self.voting_escrow, _voting_escrow)
self.voting_escrow = _voting_escrow
@external
def set_implementation(_implementation: address):
"""
@notice Set the implementation
@param _implementation The address of the implementation to use
"""
assert msg.sender == self.owner # dev: only owner
log UpdateImplementation(self.get_implementation, _implementation)
self.get_implementation = _implementation
@external
def set_mirrored(_gauge: address, _mirrored: bool):
"""
@notice Set the mirrored bit of the gauge data for `_gauge`
@param _gauge The gauge of interest
@param _mirrored Boolean deteremining whether to set the mirrored bit to True/False
"""
gauge_data: uint256 = self.gauge_data[_gauge]
assert gauge_data != 0 # dev: invalid gauge
assert msg.sender == self.owner # dev: only owner
gauge_data = shift(shift(gauge_data, -2), 2) + 1 # set is_valid_gauge = True
if _mirrored:
gauge_data += 2 # set is_mirrored = True
self.gauge_data[_gauge] = gauge_data
log UpdateMirrored(_gauge, _mirrored)
@external
def set_call_proxy(_new_call_proxy: address):
"""
@notice Set the address of the call proxy used
@dev _new_call_proxy should adhere to the same interface as defined
@param _new_call_proxy Address of the cross chain call proxy
"""
assert msg.sender == self.owner
log UpdateCallProxy(self.call_proxy, _new_call_proxy)
self.call_proxy = _new_call_proxy
@external
def commit_transfer_ownership(_future_owner: address):
"""
@notice Transfer ownership to `_future_owner`
@param _future_owner The account to commit as the future owner
"""
assert msg.sender == self.owner # dev: only owner
self.future_owner = _future_owner
@external
def accept_transfer_ownership():
"""
@notice Accept the transfer of ownership
@dev Only the committed future owner can call this function
"""
assert msg.sender == self.future_owner # dev: only future owner
log TransferOwnership(self.owner, msg.sender)
self.owner = msg.sender
@view
@external
def is_valid_gauge(_gauge: address) -> bool:
"""
@notice Query whether the gauge is a valid one deployed via the factory
@param _gauge The address of the gauge of interest
"""
return self.gauge_data[_gauge] != 0
@view
@external
def is_mirrored(_gauge: address) -> bool:
"""
@notice Query whether the gauge is mirrored on Ethereum mainnet
@param _gauge The address of the gauge of interest
"""
return (self.gauge_data[_gauge] & 2) != 0
@view
@external
def last_request(_gauge: address) -> uint256:
"""
@notice Query the timestamp of the last cross chain request for emissions
@param _gauge The address of the gauge of interest
"""
return shift(self.gauge_data[_gauge], -2)