forked from farcasterxyz/contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTierRegistry.sol
More file actions
205 lines (171 loc) · 6.73 KB
/
TierRegistry.sol
File metadata and controls
205 lines (171 loc) · 6.73 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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.29;
import {Migration} from "./abstract/Migration.sol";
import {SafeERC20, IERC20} from "openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ITierRegistry} from "./interfaces/ITierRegistry.sol";
/**
* @title Farcaster TierRegistry
*
* @notice See https://github.com/farcasterxyz/contracts/blob/v3.1.0/docs/docs.md for an overview.
*
* @custom:security-contact security@merklemanufactory.com
*/
contract TierRegistry is ITierRegistry, Migration {
using SafeERC20 for IERC20;
/*//////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc ITierRegistry
*/
string public constant VERSION = "2025.06.16";
/*//////////////////////////////////////////////////////////////
STORAGE
//////////////////////////////////////////////////////////////*/
/**
* @dev Maps tier IDs to their corresponding configuration data
* including payment parameters, duration limits,
* and activation status.
* @custom:param tierId The unique identifier for the tier
*/
mapping(uint256 tierId => TierInfo info) internal _tierInfoByTier;
/**
* @inheritdoc ITierRegistry
*/
uint256 public nextTierId = 1;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/**
* @notice Set the initial parameters and pause the contract.
*
* @param _migrator Migrator address.
* @param _initialOwner Initial owner address.
*/
constructor(address _migrator, address _initialOwner) Migration(24 hours, _migrator, _initialOwner) {}
/*//////////////////////////////////////////////////////////////
VIEWS
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc ITierRegistry
*/
function price(uint256 tier, uint256 forDays) external view returns (uint256) {
TierInfo memory info = _tierInfoByTier[tier];
if (!info.isActive) revert InvalidTier();
return info.tokenPricePerDay * forDays;
}
/**
* @inheritdoc ITierRegistry
*/
function tierInfo(
uint256 tier
) external view returns (TierInfo memory) {
return _tierInfoByTier[tier];
}
/*//////////////////////////////////////////////////////////////
TIER PURCHASING LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc ITierRegistry
*/
function purchaseTier(uint256 fid, uint256 tier, uint256 forDays) external whenNotPaused {
TierInfo memory info = _tierInfoByTier[tier];
if (forDays == 0) revert InvalidDuration();
if (!info.isActive) revert InvalidTier();
if (forDays < info.minDays) revert InvalidDuration();
if (forDays > info.maxDays) revert InvalidDuration();
uint256 cost = info.tokenPricePerDay * forDays;
emit PurchasedTier(fid, tier, forDays, msg.sender);
info.paymentToken.safeTransferFrom(msg.sender, info.vault, cost);
}
/**
* @inheritdoc ITierRegistry
*/
function batchPurchaseTier(
uint256 tier,
uint256[] calldata fids,
uint256[] calldata forDays
) external whenNotPaused {
if (fids.length == 0) revert InvalidBatchInput();
if (fids.length != forDays.length) revert InvalidBatchInput();
TierInfo memory info = _tierInfoByTier[tier];
if (!info.isActive) revert InvalidTier();
uint256 totalCost;
for (uint256 i; i < fids.length; ++i) {
uint256 numDays = forDays[i];
if (numDays == 0) revert InvalidDuration();
if (numDays < info.minDays) revert InvalidDuration();
if (numDays > info.maxDays) revert InvalidDuration();
totalCost += info.tokenPricePerDay * numDays;
emit PurchasedTier(fids[i], tier, forDays[i], msg.sender);
}
info.paymentToken.safeTransferFrom(msg.sender, info.vault, totalCost);
}
/*//////////////////////////////////////////////////////////////
PERMISSIONED ACTIONS
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc ITierRegistry
*/
function batchCreditTier(uint256 tier, uint256[] calldata fids, uint256 forDays) external onlyMigrator {
if (fids.length == 0) revert InvalidBatchInput();
TierInfo memory info = _tierInfoByTier[tier];
if (!info.isActive) revert InvalidTier();
for (uint256 i; i < fids.length; ++i) {
if (forDays == 0) revert InvalidDuration();
if (forDays < info.minDays) revert InvalidDuration();
if (forDays > info.maxDays) revert InvalidDuration();
emit PurchasedTier(fids[i], tier, forDays, msg.sender);
}
}
/**
* @inheritdoc ITierRegistry
*/
function setTier(
uint256 tier,
address paymentToken,
uint256 minDays,
uint256 maxDays,
uint256 tokenPricePerDay,
address vault
) external onlyOwner {
if (paymentToken == address(0)) revert InvalidTokenAddress();
if (minDays == 0) revert InvalidDuration();
if (maxDays == 0) revert InvalidDuration();
if (minDays > maxDays) revert InvalidDuration();
if (tokenPricePerDay == 0) revert InvalidPrice();
if (vault == address(0)) revert InvalidVaultAddress();
if (tier == 0) revert InvalidTier();
if (tier > nextTierId) revert InvalidTier();
emit SetTier(tier, minDays, maxDays, vault, paymentToken, tokenPricePerDay);
_tierInfoByTier[tier] = TierInfo({
minDays: minDays,
maxDays: maxDays,
paymentToken: IERC20(paymentToken),
tokenPricePerDay: tokenPricePerDay,
vault: vault,
isActive: true
});
if (tier == nextTierId) {
nextTierId += 1;
}
}
/**
* @inheritdoc ITierRegistry
*/
function deactivateTier(
uint256 tier
) external onlyOwner {
if (!_tierInfoByTier[tier].isActive) revert InvalidTier();
emit DeactivateTier(tier);
_tierInfoByTier[tier].isActive = false;
}
/**
* @inheritdoc ITierRegistry
*/
function sweepToken(address token, address to) external onlyOwner {
uint256 balance = IERC20(token).balanceOf(address(this));
emit SweepToken(token, to, balance);
IERC20(token).safeTransfer(to, balance);
}
}