generated from ZeframLou/foundry-template
/
VeRecipient.sol
159 lines (132 loc) · 6.11 KB
/
VeRecipient.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
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.4;
import {CrossChainEnabled} from "openzeppelin-contracts/contracts/crosschain/CrossChainEnabled.sol";
import "./base/Structs.sol";
import {BoringOwnable} from "./base/BoringOwnable.sol";
/// @title VeRecipient
/// @author zefram.eth
/// @notice Recipient on non-Ethereum networks that receives data from the Ethereum beacon
/// and makes vetoken balances available on this network.
abstract contract VeRecipient is CrossChainEnabled, BoringOwnable {
/// -----------------------------------------------------------------------
/// Errors
/// -----------------------------------------------------------------------
error VeRecipient__InvalidInput();
/// -----------------------------------------------------------------------
/// Events
/// -----------------------------------------------------------------------
event UpdateVeBalance(address indexed user);
event SetBeacon(address indexed newBeacon);
/// -----------------------------------------------------------------------
/// Constants
/// -----------------------------------------------------------------------
uint256 internal constant MAX_ITERATIONS = 255;
/// -----------------------------------------------------------------------
/// Storage variables
/// -----------------------------------------------------------------------
address public beacon;
mapping(address => Point) public userData;
Point public globalData;
mapping(uint256 => int128) public slopeChanges;
/// -----------------------------------------------------------------------
/// Constructor
/// -----------------------------------------------------------------------
constructor(address beacon_, address owner_) BoringOwnable(owner_) {
beacon = beacon_;
}
/// -----------------------------------------------------------------------
/// Crosschain functions
/// -----------------------------------------------------------------------
/// @notice Called by VeBeacon from Ethereum via bridge to update vetoken balance & supply info.
function updateVeBalance(
address user,
int128 userBias,
int128 userSlope,
uint256 userTs,
int128 globalBias,
int128 globalSlope,
uint256 globalTs,
SlopeChange[] calldata slopeChanges_
) external onlyCrossChainSender(beacon) {
userData[user] = Point({bias: userBias, slope: userSlope, ts: userTs});
globalData = Point({bias: globalBias, slope: globalSlope, ts: globalTs});
uint256 slopeChangesLength = slopeChanges_.length;
for (uint256 i; i < slopeChangesLength;) {
slopeChanges[slopeChanges_[i].ts] = slopeChanges_[i].change;
unchecked {
++i;
}
}
emit UpdateVeBalance(user);
}
/// -----------------------------------------------------------------------
/// Owner functions
/// -----------------------------------------------------------------------
/// @notice Called by owner to update the beacon address.
/// @dev The beacon address needs to be updateable because VeBeacon needs to be redeployed
/// when support for a new network is added.
/// @param newBeacon The new address
function setBeacon(address newBeacon) external onlyOwner {
if (newBeacon == address(0)) revert VeRecipient__InvalidInput();
beacon = newBeacon;
emit SetBeacon(newBeacon);
}
/// -----------------------------------------------------------------------
/// View functions
/// -----------------------------------------------------------------------
/// @notice Computes the vetoken balance of a user. Returns 0 if the user's data hasn't
/// been broadcasted from VeBeacon. Exhibits the same time-decay behavior as regular
/// VotingEscrow contracts.
/// @param user The user address to query
/// @return The user's vetoken balance.
function balanceOf(address user) external view returns (uint256) {
// storage loads
Point memory u = userData[user];
// compute vetoken balance
int256 veBalance = u.bias - u.slope * int128(int256(block.timestamp - u.ts));
if (veBalance < 0) veBalance = 0;
return uint256(veBalance);
}
/// @notice Computes the total supply of the vetoken. Returns 0 if data hasn't
/// been broadcasted from VeBeacon. Exhibits the same time-decay behavior as regular
/// VotingEscrow contracts.
/// @dev The value may diverge from the correct value if `updateVeBalance()` hasn't been
/// called for 8 consecutive epochs (~2 months). This is because we limit the size of each
/// slopeChanges update to limit gas costs.
/// @return The vetoken's total supply
function totalSupply() external view returns (uint256) {
Point memory g = globalData;
uint256 ti = (g.ts / (1 weeks)) * (1 weeks);
for (uint256 i; i < MAX_ITERATIONS;) {
ti += 1 weeks;
int128 slopeChange;
if (ti > block.timestamp) {
ti = block.timestamp;
} else {
slopeChange = slopeChanges[ti];
}
g.bias -= g.slope * int128(int256(ti - g.ts));
if (ti == block.timestamp) break;
g.slope += slopeChange;
g.ts = ti;
unchecked {
++i;
}
}
if (g.bias < 0) g.bias = 0;
return uint256(uint128(g.bias));
}
/// @notice Returns the timestamp a user's vetoken position was last updated. Returns 0 if the user's data
/// has never been broadcasted.
/// @dev Added for compatibility with kick() in gauge contracts.
/// @param user The user's address
/// @return The last update timestamp
function user_point_history__ts(address user, uint256 /*epoch*/ ) external view returns (uint256) {
return userData[user].ts;
}
/// @notice Just returns 0.
/// @dev Added for compatibility with kick() in gauge contracts.
function user_point_epoch(address /*user*/ ) external pure returns (uint256) {
return 0;
}
}