1+ # @version 0.2.7
2+ """
3+ @title Curve Fee Estimate
4+ @author Ocean Protocol
5+ @license MIT
6+ """
7+
8+ from vyper.interfaces import ERC20
9+
10+
11+ interface VotingEscrow :
12+ def user_point_epoch (addr: address ) -> uint256 : view
13+ def epoch () -> uint256 : view
14+ def user_point_history (addr: address , loc: uint256 ) -> Point: view
15+ def point_history (loc: uint256 ) -> Point: view
16+ def checkpoint (): nonpayable
17+
18+ interface FeeDistributor :
19+ def last_token_time () -> uint256 : view
20+ def start_time () -> uint256 : view
21+ def time_cursor () -> uint256 : view
22+ def time_cursor_of (addr: address ) -> uint256 : view
23+ def user_epoch_of (addr: address ) -> uint256 : view
24+ def tokens_per_week (week: uint256 ) -> uint256 : view
25+ def ve_supply (week: uint256 ) -> uint256 : view
26+
27+ struct Point :
28+ bias: int128
29+ slope: int128 # - dweight / dt
30+ ts: uint256
31+ blk: uint256 # block
32+
33+
34+ WEEK: constant (uint256 ) = 7 * 86400
35+ TOKEN_CHECKPOINT_DEADLINE: constant (uint256 ) = 86400
36+
37+ voting_escrow: public (address )
38+ fee_distributor: public (address )
39+
40+
41+
42+ @external
43+ def __init__ (
44+ _voting_escrow: address ,
45+ _fee_distributor: address ,
46+ ):
47+ """
48+ @notice Contract constructor
49+ @param _voting_escrow VotingEscrow contract address
50+ @param _fee_distributor FeeDistributor contract address
51+ """
52+ self .voting_escrow = _voting_escrow
53+ self .fee_distributor = _fee_distributor
54+
55+ @internal
56+ def _find_timestamp_epoch (ve: address , _timestamp: uint256 ) -> uint256 :
57+ _min: uint256 = 0
58+ _max: uint256 = VotingEscrow (ve).epoch ()
59+ for i in range (128 ):
60+ if _min >= _max:
61+ break
62+ _mid: uint256 = (_min + _max + 2 ) / 2
63+ pt: Point = VotingEscrow (ve).point_history (_mid)
64+ if pt.ts <= _timestamp:
65+ _min = _mid
66+ else :
67+ _max = _mid - 1
68+ return _min
69+
70+ @view
71+ @internal
72+ def _find_timestamp_user_epoch (ve: address , user: address , _timestamp: uint256 , max_user_epoch: uint256 ) -> uint256 :
73+ _min: uint256 = 0
74+ _max: uint256 = max_user_epoch
75+ for i in range (128 ):
76+ if _min >= _max:
77+ break
78+ _mid: uint256 = (_min + _max + 2 ) / 2
79+ pt: Point = VotingEscrow (ve).user_point_history (user, _mid)
80+ if pt.ts <= _timestamp:
81+ _min = _mid
82+ else :
83+ _max = _mid - 1
84+ return _min
85+
86+
87+
88+ @external
89+ @view
90+ def estimateClaim (addr: address ) -> uint256 :
91+ # Minimal user_epoch is 0 (if user had no point)
92+ user_epoch: uint256 = 0
93+ to_distribute: uint256 = 0
94+
95+ max_user_epoch: uint256 = VotingEscrow (self .voting_escrow).user_point_epoch (addr)
96+ _start_time: uint256 = FeeDistributor (self .fee_distributor).start_time ()
97+ _last_token_time: uint256 = FeeDistributor (self .fee_distributor).last_token_time ()
98+
99+ if max_user_epoch == 0 :
100+ # No lock = no fees
101+ return 0
102+
103+ week_cursor: uint256 = FeeDistributor (self .fee_distributor).time_cursor_of (addr)
104+ if week_cursor == 0 :
105+ # Need to do the initial binary search
106+ user_epoch = self ._find_timestamp_user_epoch (self .voting_escrow, addr, _start_time, max_user_epoch)
107+ else :
108+ user_epoch = FeeDistributor (self .fee_distributor).user_epoch_of (addr)
109+
110+ if user_epoch == 0 :
111+ user_epoch = 1
112+
113+ user_point: Point = VotingEscrow (self .voting_escrow).user_point_history (addr, user_epoch)
114+
115+ if week_cursor == 0 :
116+ week_cursor = (user_point.ts + WEEK - 1 ) / WEEK * WEEK
117+
118+ if week_cursor >= _last_token_time:
119+ return 0
120+
121+ if week_cursor < _start_time:
122+ week_cursor = _start_time
123+ old_user_point: Point = empty (Point)
124+
125+ # Iterate over weeks
126+ for i in range (50 ):
127+ if week_cursor >= _last_token_time:
128+ break
129+
130+ if week_cursor >= user_point.ts and user_epoch <= max_user_epoch:
131+ user_epoch += 1
132+ old_user_point = user_point
133+ if user_epoch > max_user_epoch:
134+ user_point = empty (Point)
135+ else :
136+ user_point = VotingEscrow (self .voting_escrow).user_point_history (addr, user_epoch)
137+
138+ else :
139+ # Calc
140+ # + i * 2 is for rounding errors
141+ dt: int128 = convert (week_cursor - old_user_point.ts, int128 )
142+ balance_of: uint256 = convert (max (old_user_point.bias - dt * old_user_point.slope, 0 ), uint256 )
143+ if balance_of == 0 and user_epoch > max_user_epoch:
144+ break
145+ if balance_of > 0 :
146+ tokens_per_week: uint256 = FeeDistributor (self .fee_distributor).tokens_per_week (week_cursor)
147+ ve_supply: uint256 = FeeDistributor (self .fee_distributor).ve_supply (week_cursor)
148+ if ve_supply != 0 and tokens_per_week != 0 :
149+ to_distribute += balance_of * tokens_per_week/ ve_supply
150+
151+ week_cursor += WEEK
152+
153+
154+ return to_distribute
0 commit comments