-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy pathVestingEscrowFactory.vy
More file actions
259 lines (224 loc) · 7.17 KB
/
Copy pathVestingEscrowFactory.vy
File metadata and controls
259 lines (224 loc) · 7.17 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
#pragma version 0.4.3
#pragma evm-version prague
"""
@title Vesting Escrow Factory
@author Curve Finance, Yearn Finance
@license MIT
@notice Deploys dedicated ERC-20 and ERC-4626 minimal-proxy vesting escrows
"""
from ethereum.ercs import IERC20
interface IVestingEscrowSimple:
def initialize(
revoker: address,
token: IERC20,
recipient: address,
amount: uint256,
start_time: uint256,
end_time: uint256,
cliff_length: uint256,
permissionless_claims: bool,
) -> bool: nonpayable
interface IVestingEscrow4626:
def initialize(
revoker: address,
vault: address,
recipient: address,
principal_assets: uint256,
start_time: uint256,
end_time: uint256,
cliff_length: uint256,
permissionless_claims: bool,
yield_recipient: address,
) -> bool: nonpayable
interface IERC4626:
def asset() -> address: view
def convertToAssets(shares: uint256) -> uint256: view
def convertToShares(assets: uint256) -> uint256: view
event TokenVestingEscrowCreated:
escrow: indexed(address)
token: indexed(IERC20)
recipient: indexed(address)
funder: address
revoker: address
amount: uint256
vesting_start: uint256
vesting_duration: uint256
cliff_length: uint256
permissionless_claims: bool
event ERC4626VestingEscrowCreated:
escrow: indexed(address)
vault: indexed(IERC20)
recipient: indexed(address)
funder: address
revoker: address
yield_recipient: address
asset_token: address
funded_shares: uint256
principal_assets: uint256
vesting_start: uint256
vesting_duration: uint256
cliff_length: uint256
permissionless_claims: bool
MAX_DURATION: constant(uint256) = 2**64 - 1
MAX_PRINCIPAL: constant(uint256) = 2**128 - 1
STANDARD_TARGET: public(immutable(address))
ERC4626_TARGET: public(immutable(address))
@deploy
def __init__(
standard_target: address,
erc4626_target: address,
):
assert standard_target.is_contract # dev: invalid standard target
assert erc4626_target.is_contract # dev: invalid erc4626 target
assert standard_target != erc4626_target # dev: duplicate target
STANDARD_TARGET = standard_target
ERC4626_TARGET = erc4626_target
@internal
@view
def _validate(
token: IERC20,
recipient: address,
amount: uint256,
vesting_duration: uint256,
vesting_start: uint256,
cliff_length: uint256,
revoker: address,
):
assert amount > 0 # dev: amount must be > 0
assert vesting_duration > 0 # dev: invalid vesting period
assert vesting_duration <= MAX_DURATION # dev: duration too long
assert cliff_length <= vesting_duration # dev: invalid cliff
assert vesting_start + vesting_duration > block.timestamp # dev: invalid vesting period
assert recipient not in [empty(address), self, token.address, revoker] # dev: invalid recipient
@internal
def _fund(
token: IERC20,
funder: address,
escrow: address,
amount: uint256,
):
balance_before: uint256 = staticcall token.balanceOf(escrow)
assert extcall token.transferFrom(funder, escrow, amount, default_return_value=True) # dev: funding failed
balance_after: uint256 = staticcall token.balanceOf(escrow)
assert balance_after >= balance_before and balance_after - balance_before == amount # dev: incorrect funding
@internal
@view
def _erc4626_funding_shares(
vault: IERC4626,
principal_assets: uint256,
) -> uint256:
assert principal_assets > 0 # dev: principal must be > 0
assert principal_assets <= MAX_PRINCIPAL # dev: principal too large
funded_shares: uint256 = staticcall vault.convertToShares(principal_assets)
assert funded_shares > 0 # dev: invalid conversion
if staticcall vault.convertToAssets(funded_shares) < principal_assets:
funded_shares += 1
assert staticcall vault.convertToAssets(funded_shares) >= principal_assets # dev: invalid conversion
return funded_shares
@external
@view
def preview_erc4626_funding(
vault: IERC4626,
principal_assets: uint256,
) -> uint256:
"""Quote the shares required to fully back an asset-denominated principal."""
return self._erc4626_funding_shares(vault, principal_assets)
@external
@nonreentrant
def deploy_vesting_contract(
token: IERC20,
recipient: address,
amount: uint256,
vesting_duration: uint256,
vesting_start: uint256,
cliff_length: uint256,
permissionless_claims: bool,
revoker: address,
) -> address:
"""Deploy a standard ERC-20 vesting escrow."""
assert amount <= MAX_PRINCIPAL # dev: amount too large
self._validate(token, recipient, amount, vesting_duration, vesting_start, cliff_length, revoker)
escrow: address = create_minimal_proxy_to(STANDARD_TARGET)
self._fund(token, msg.sender, escrow, amount)
assert extcall IVestingEscrowSimple(escrow).initialize(
revoker,
token,
recipient,
amount,
vesting_start,
vesting_start + vesting_duration,
cliff_length,
permissionless_claims,
)
log TokenVestingEscrowCreated(
escrow=escrow,
token=token,
recipient=recipient,
funder=msg.sender,
revoker=revoker,
amount=amount,
vesting_start=vesting_start,
vesting_duration=vesting_duration,
cliff_length=cliff_length,
permissionless_claims=permissionless_claims,
)
return escrow
@external
@nonreentrant
def deploy_erc4626_vesting(
vault: IERC20,
recipient: address,
principal_assets: uint256,
max_funded_shares: uint256,
vesting_duration: uint256,
vesting_start: uint256,
cliff_length: uint256,
permissionless_claims: bool,
revoker: address,
yield_recipient: address,
) -> address:
"""Deploy an ERC-4626 principal vesting escrow."""
self._validate(
vault,
recipient,
principal_assets,
vesting_duration,
vesting_start,
cliff_length,
revoker,
)
assert yield_recipient != empty(address) # dev: invalid yield recipient
funded_shares: uint256 = self._erc4626_funding_shares(
IERC4626(vault.address),
principal_assets,
)
assert funded_shares <= max_funded_shares # dev: share limit exceeded
escrow: address = create_minimal_proxy_to(ERC4626_TARGET)
self._fund(vault, msg.sender, escrow, funded_shares)
assert extcall IVestingEscrow4626(escrow).initialize(
revoker,
vault.address,
recipient,
principal_assets,
vesting_start,
vesting_start + vesting_duration,
cliff_length,
permissionless_claims,
yield_recipient,
)
log ERC4626VestingEscrowCreated(
escrow=escrow,
vault=vault,
recipient=recipient,
funder=msg.sender,
revoker=revoker,
yield_recipient=yield_recipient,
asset_token=staticcall IERC4626(vault.address).asset(),
funded_shares=funded_shares,
principal_assets=principal_assets,
vesting_start=vesting_start,
vesting_duration=vesting_duration,
cliff_length=cliff_length,
permissionless_claims=permissionless_claims,
)
return escrow