forked from kalidao/rage-router
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Ragequitter.sol
329 lines (275 loc) · 12.4 KB
/
Ragequitter.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
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
// ᗪᗩGOᑎ 𒀭 𒀭 𒀭 𒀭 𒀭 𒀭 𒀭 𒀭 𒀭 𒀭 𒀭
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;
import {ERC6909} from "@solady/src/tokens/ERC6909.sol";
/// @notice Simple ragequit singleton with ERC6909 accounting. Version 1.
contract Ragequitter is ERC6909 {
/// ======================= CUSTOM ERRORS ======================= ///
/// @dev Invalid time window for ragequit.
error InvalidTime();
/// @dev Out-of-order redemption assets.
error InvalidAssetOrder();
/// @dev Overflow or division by zero.
error MulDivFailed();
/// @dev ERC20 `transferFrom` failed.
error TransferFromFailed();
/// @dev ETH transfer failed.
error ETHTransferFailed();
/// =========================== EVENTS =========================== ///
/// @dev Logs new account loot metadata.
event URI(string metadata, uint256 indexed id);
/// @dev Logs new account authority contract.
event AuthSet(address indexed account, IAuth authority);
/// @dev Logs new account contribution asset setting.
event TributeSet(address indexed account, address tribute);
/// @dev Logs new account ragequit time validity setting.
event TimeValiditySet(address indexed account, uint48 validAfter, uint48 validUntil);
/// ========================== STRUCTS ========================== ///
/// @dev The account loot shares metadata struct.
struct Metadata {
string name;
string symbol;
string tokenURI;
IAuth authority;
uint96 totalSupply;
}
/// @dev The account loot shares ownership struct.
struct Ownership {
address owner;
uint96 shares;
}
/// @dev The account loot shares settings struct.
struct Settings {
address tribute;
uint48 validAfter;
uint48 validUntil;
}
/// ========================= CONSTANTS ========================= ///
/// @dev The conventional ERC7528 ETH address.
address internal constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// ========================== STORAGE ========================== ///
/// @dev Stores mapping of metadata settings to account token IDs.
/// note: IDs are unique to addresses (`uint256(uint160(account))`).
mapping(uint256 id => Metadata) internal _metadata;
/// @dev Stores mapping of ragequit settings to accounts.
mapping(address account => Settings) internal _settings;
/// ================= ERC6909 METADATA & SUPPLY ================= ///
/// @dev Returns the name for token `id` using this contract.
function name(uint256 id) public view virtual override(ERC6909) returns (string memory) {
return _metadata[id].name;
}
/// @dev Returns the symbol for token `id` using this contract.
function symbol(uint256 id) public view virtual override(ERC6909) returns (string memory) {
return _metadata[id].symbol;
}
/// @dev Returns the URI for token `id` using this contract.
function tokenURI(uint256 id) public view virtual override(ERC6909) returns (string memory) {
return _metadata[id].tokenURI;
}
/// @dev Returns the total supply for token `id` using this contract.
function totalSupply(uint256 id) public view virtual returns (uint256) {
return _metadata[id].totalSupply;
}
/// ========================== RAGEQUIT ========================== ///
/// @dev Ragequits `shares` of `account` loot for their current fair share of pooled `assets`.
function ragequit(address account, uint96 shares, address[] calldata assets) public virtual {
Settings storage setting = _settings[account];
if (block.timestamp < setting.validAfter) revert InvalidTime();
if (block.timestamp > setting.validUntil) revert InvalidTime();
if (assets.length == 0) revert InvalidAssetOrder();
uint256 id = uint256(uint160(account));
uint256 supply = _metadata[id].totalSupply;
unchecked {
_metadata[id].totalSupply -= shares;
}
_burn(msg.sender, id, shares);
address asset;
address prev;
uint256 share;
for (uint256 i; i != assets.length; ++i) {
asset = assets[i];
if (asset <= prev) revert InvalidAssetOrder();
prev = asset;
share = _mulDiv(shares, _balanceOf(asset, account), supply);
if (share != 0) _safeTransferFrom(asset, account, msg.sender, share);
}
}
/// @dev Returns `floor(x * y / d)`.
/// Reverts if `x * y` overflows, or `d` is zero.
function _mulDiv(uint256 x, uint256 y, uint256 d) internal pure virtual returns (uint256 z) {
assembly ("memory-safe") {
// Equivalent to require(d != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(d, iszero(mul(y, gt(x, div(not(0), y)))))) {
mstore(0x00, 0xad251c27) // `MulDivFailed()`.
revert(0x1c, 0x04)
}
z := div(mul(x, y), d)
}
}
/// ============================ LOOT ============================ ///
/// @dev Mints loot shares for an owner of the caller account.
function mint(address owner, uint96 shares) public virtual {
uint256 id = uint256(uint160(msg.sender));
_metadata[id].totalSupply += shares;
_mint(owner, id, shares);
}
/// @dev Burns loot shares from an owner of the caller account.
function burn(address owner, uint96 shares) public virtual {
uint256 id = uint256(uint160(msg.sender));
unchecked {
_metadata[id].totalSupply -= shares;
}
_burn(owner, id, shares);
}
/// ========================== TRIBUTE ========================== ///
/// @dev Mints loot shares in exchange for tribute `amount` to an `account`.
/// If no `tribute` is set, then function will revert on `safeTransferFrom`.
function contribute(address account, uint96 amount) public payable virtual {
address tribute = _settings[account].tribute;
if (tribute == ETH) _safeTransferETH(account, amount);
else _safeTransferFrom(tribute, msg.sender, account, amount);
uint256 id = uint256(uint160(account));
_metadata[id].totalSupply += amount;
_mint(msg.sender, id, amount);
}
/// ======================== INSTALLATION ======================== ///
/// @dev Initializes ragequit settings for the caller account.
function install(Ownership[] calldata owners, Settings calldata setting, Metadata calldata meta)
public
virtual
{
uint256 id = uint256(uint160(msg.sender));
if (owners.length != 0) {
uint96 supply;
for (uint256 i; i != owners.length; ++i) {
supply += owners[i].shares;
_mint(owners[i].owner, id, owners[i].shares);
}
_metadata[id].totalSupply += supply;
}
if (bytes(meta.name).length != 0) {
_metadata[id].name = meta.name;
_metadata[id].symbol = meta.symbol;
}
if (bytes(meta.tokenURI).length != 0) {
emit URI((_metadata[id].tokenURI = meta.tokenURI), id);
}
if (meta.authority != IAuth(address(0))) {
emit AuthSet(msg.sender, (_metadata[id].authority = meta.authority));
}
_settings[msg.sender] = Settings(setting.tribute, setting.validAfter, setting.validUntil);
emit TimeValiditySet(msg.sender, setting.validAfter, setting.validUntil);
emit TributeSet(msg.sender, setting.tribute);
}
/// ==================== SETTINGS & METADATA ==================== ///
/// @dev Returns the account metadata.
function getMetadata(address account)
public
view
virtual
returns (string memory, string memory, string memory, IAuth)
{
Metadata storage meta = _metadata[uint256(uint160(account))];
return (meta.name, meta.symbol, meta.tokenURI, meta.authority);
}
/// @dev Returns the account tribute and ragequit time validity settings.
function getSettings(address account) public view virtual returns (address, uint48, uint48) {
Settings storage setting = _settings[account];
return (setting.tribute, setting.validAfter, setting.validUntil);
}
/// @dev Sets new authority contract for the caller account.
function setAuth(IAuth authority) public virtual {
emit AuthSet(msg.sender, (_metadata[uint256(uint160(msg.sender))].authority = authority));
}
/// @dev Sets account and loot token URI `metadata`.
function setURI(string calldata metadata) public virtual {
uint256 id = uint256(uint160(msg.sender));
emit URI((_metadata[id].tokenURI = metadata), id);
}
/// @dev Sets account ragequit time validity (or 'time window').
function setTimeValidity(uint48 validAfter, uint48 validUntil) public virtual {
emit TimeValiditySet(
msg.sender,
_settings[msg.sender].validAfter = validAfter,
_settings[msg.sender].validUntil = validUntil
);
}
/// @dev Sets account contribution asset (tribute).
function setTribute(address tribute) public virtual {
emit TributeSet(msg.sender, _settings[msg.sender].tribute = tribute);
}
/// =================== EXTERNAL ASSET HELPERS =================== ///
/// @dev Returns the `amount` of ERC20 `token` owned by `account`.
/// Returns zero if the `token` does not exist.
function _balanceOf(address token, address account)
internal
view
virtual
returns (uint256 amount)
{
assembly ("memory-safe") {
mstore(0x14, account) // Store the `account` argument.
mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
amount :=
mul( // The arguments of `mul` are evaluated from right to left.
mload(0x20),
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
)
}
}
/// @dev Sends `amount` (in wei) ETH to `to`.
function _safeTransferETH(address to, uint256 amount) internal virtual {
assembly ("memory-safe") {
if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
function _safeTransferFrom(address token, address from, address to, uint256 amount)
internal
virtual
{
assembly ("memory-safe") {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// ========================= OVERRIDES ========================= ///
/// @dev Hook that is called before any transfer of tokens.
/// This includes minting and burning. Also requests authority for token transfers.
function _beforeTokenTransfer(address from, address to, uint256 id, uint256 amount)
internal
virtual
override(ERC6909)
{
IAuth authority = _metadata[id].authority;
if (authority != IAuth(address(0))) authority.validateTransfer(from, to, id, amount);
}
}
/// @notice Simple authority interface for contracts.
interface IAuth {
function validateTransfer(address, address, uint256, uint256)
external
payable
returns (uint256);
}